diff --git a/.gitignore b/.gitignore
index b568df4..d8a14f5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,7 +21,7 @@ migrate_working_dir/
# 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
# is commented out by default.
-#.vscode/
+.vscode/
# Flutter/Dart/Pub related
**/doc/api/
@@ -41,6 +41,11 @@ app.*.symbols
# Obfuscation related
app.*.map.json
+#AI generated
+CLAUDE.md
+/.claude
+
+
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
@@ -53,7 +58,6 @@ app.*.map.json
/android/core/**/cmake-build-*/
/android/core/**/jniLibs/
-
#FlClash
/libclash/
/android/app/src/main/jniLibs/
@@ -61,3 +65,6 @@ app.*.map.json
/macos/**/Package.resolved
devtools_options.yaml
+# FVM Version Cache
+.fvm/
+.fvmrc
\ No newline at end of file
diff --git a/.metadata b/.metadata
index 30d90a7..2e80cfe 100644
--- a/.metadata
+++ b/.metadata
@@ -1,11 +1,11 @@
# This file tracks properties of this Flutter project.
# 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:
- revision: 796c8ef79279f9c774545b3771238c3098dbefab
- channel: stable
+ revision: "adc901062556672b4138e18a4dc62a4be8f4b3c2"
+ channel: "stable"
project_type: app
@@ -13,26 +13,11 @@ project_type: app
migration:
platforms:
- platform: root
- create_revision: 796c8ef79279f9c774545b3771238c3098dbefab
- base_revision: 796c8ef79279f9c774545b3771238c3098dbefab
- - 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
+ create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2
+ base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2
- platform: windows
- create_revision: 796c8ef79279f9c774545b3771238c3098dbefab
- base_revision: 796c8ef79279f9c774545b3771238c3098dbefab
+ create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2
+ base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2
# User provided section
diff --git a/.run/main.dart.run.xml b/.run/main.dart.run.xml
new file mode 100644
index 0000000..7a9c9da
--- /dev/null
+++ b/.run/main.dart.run.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Makefile b/Makefile
deleted file mode 100644
index 627966f..0000000
--- a/Makefile
+++ /dev/null
@@ -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
\ No newline at end of file
diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts
index 39e11a5..4cef3e8 100644
--- a/android/app/build.gradle.kts
+++ b/android/app/build.gradle.kts
@@ -64,16 +64,17 @@ android {
buildTypes {
debug {
isMinifyEnabled = false
- applicationIdSuffix = ".debug"
+ applicationIdSuffix = ".dev"
}
release {
isMinifyEnabled = true
isShrinkResources = true
- signingConfig = if (isRelease) {
- signingConfigs.getByName("release")
+ if (isRelease) {
+ signingConfig = signingConfigs.getByName("release")
} else {
- signingConfigs.getByName("debug")
+ signingConfig = signingConfigs.getByName("debug")
+ applicationIdSuffix = ".dev"
}
proguardFiles(
diff --git a/android/app/google-services.json b/android/app/google-services.json
index 63581b3..ec7efbe 100644
--- a/android/app/google-services.json
+++ b/android/app/google-services.json
@@ -41,6 +41,25 @@
"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": []
+ }
+ }
}
]
}
\ No newline at end of file
diff --git a/android/app/src/main/kotlin/com/follow/clash/Ext.kt b/android/app/src/main/kotlin/com/follow/clash/Ext.kt
index b6d6bcd..7cabf5b 100644
--- a/android/app/src/main/kotlin/com/follow/clash/Ext.kt
+++ b/android/app/src/main/kotlin/com/follow/clash/Ext.kt
@@ -1,13 +1,18 @@
package com.follow.clash
+import android.app.Application
+import android.content.Context.MODE_PRIVATE
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.drawable.Drawable
import android.os.Build
import android.os.Handler
import android.os.Looper
+import android.widget.Toast
import androidx.core.graphics.drawable.toBitmap
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.plugins.FlutterPlugin
import io.flutter.plugin.common.MethodChannel
@@ -21,6 +26,30 @@ import kotlin.coroutines.resume
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 =
withContext(Dispatchers.IO) {
val cacheDir = GlobalState.application.cacheDir
@@ -118,4 +147,4 @@ fun MethodChannel.invokeMethodOnMainThread(
}
})
}
-}
\ No newline at end of file
+}
diff --git a/android/app/src/main/kotlin/com/follow/clash/MainActivity.kt b/android/app/src/main/kotlin/com/follow/clash/MainActivity.kt
index 8c841ae..c5760d2 100644
--- a/android/app/src/main/kotlin/com/follow/clash/MainActivity.kt
+++ b/android/app/src/main/kotlin/com/follow/clash/MainActivity.kt
@@ -1,7 +1,6 @@
package com.follow.clash
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.ServicePlugin
@@ -18,9 +17,6 @@ class MainActivity : FlutterActivity(),
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- lifecycleScope.launch {
- State.destroyServiceEngine()
- }
}
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
diff --git a/android/app/src/main/kotlin/com/follow/clash/Service.kt b/android/app/src/main/kotlin/com/follow/clash/Service.kt
index 758818a..9385b55 100644
--- a/android/app/src/main/kotlin/com/follow/clash/Service.kt
+++ b/android/app/src/main/kotlin/com/follow/clash/Service.kt
@@ -1,5 +1,6 @@
package com.follow.clash
+import com.follow.clash.common.GlobalState
import com.follow.clash.common.ServiceDelegate
import com.follow.clash.common.formatString
import com.follow.clash.common.intent
@@ -8,6 +9,7 @@ import com.follow.clash.service.ICallbackInterface
import com.follow.clash.service.IEventInterface
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.models.NotificationParams
import com.follow.clash.service.models.VpnOptions
@@ -40,7 +42,7 @@ object Service {
delegate.unbind()
}
- suspend fun invokeAction(data: String, cb: (result: String) -> Unit): Result {
+ suspend fun invokeAction(data: String, cb: ((result: String) -> Unit)?): Result {
val res = mutableListOf()
return delegate.useService {
it.invokeAction(
@@ -51,13 +53,50 @@ object Service {
res.add(result ?: byteArrayOf())
ack?.onAck()
if (isSuccess) {
- cb(res.formatString())
+ cb?.let { cb ->
+ cb(res.formatString())
+ }
}
}
})
}
}
+ suspend fun quickSetup(
+ initParamsString: String,
+ setupParamsString: String,
+ onStarted: (() -> Unit)?,
+ onResult: ((result: String) -> Unit)?,
+ ): Result {
+ val res = mutableListOf()
+ 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 {
@@ -65,24 +104,24 @@ object Service {
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)
+ 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
- })
+ false -> null
+ })
}
}
@@ -116,6 +155,7 @@ object Service {
try {
block(callback)
} catch (e: Exception) {
+ GlobalState.log("awaitIResultInterface $e")
if (continuation.isActive) {
continuation.resumeWithException(e)
}
diff --git a/android/app/src/main/kotlin/com/follow/clash/State.kt b/android/app/src/main/kotlin/com/follow/clash/State.kt
index cb5be3e..f3e1b47 100644
--- a/android/app/src/main/kotlin/com/follow/clash/State.kt
+++ b/android/app/src/main/kotlin/com/follow/clash/State.kt
@@ -1,18 +1,17 @@
package com.follow.clash
+import android.net.VpnService
import com.follow.clash.common.GlobalState
+import com.follow.clash.models.SharedState
import com.follow.clash.plugins.AppPlugin
-import com.follow.clash.plugins.ServicePlugin
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.dart.DartExecutor
-import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
-import kotlinx.coroutines.withContext
enum class RunState {
START, PENDING, STOP
@@ -25,20 +24,17 @@ object State {
var runTime: Long = 0
+ var sharedState: SharedState = SharedState()
+
val runStateFlow: MutableStateFlow = MutableStateFlow(RunState.STOP)
var flutterEngine: FlutterEngine? = null
- var serviceFlutterEngine: FlutterEngine? = null
val appPlugin: AppPlugin?
- get() = flutterEngine?.plugin() ?: serviceFlutterEngine?.plugin()
-
- val servicePlugin: ServicePlugin?
- get() = flutterEngine?.plugin()
- ?: serviceFlutterEngine?.plugin()
+ get() = flutterEngine?.plugin()
val tilePlugin: TilePlugin?
- get() = flutterEngine?.plugin() ?: serviceFlutterEngine?.plugin()
+ get() = flutterEngine?.plugin()
suspend fun handleToggleAction() {
var action: (suspend () -> Unit)?
@@ -77,7 +73,7 @@ object State {
if (flutterEngine != null) {
return
}
- startServiceWithEngine()
+ startServiceWithPref()
}
}
@@ -88,9 +84,10 @@ object State {
return
}
tilePlugin?.handleStop()
- if (flutterEngine != null || serviceFlutterEngine != null) {
+ if (flutterEngine != null) {
return
}
+ GlobalState.application.showToast(sharedState.stopTip)
handleStopService()
}
}
@@ -106,72 +103,101 @@ object State {
startService()
}
- fun handleStopService() {
- GlobalState.launch {
- runLock.withLock {
- if (runStateFlow.value != RunState.START) {
- return@launch
- }
- runStateFlow.tryEmit(RunState.PENDING)
- runTime = Service.stopService()
- runStateFlow.tryEmit(RunState.STOP)
- }
- destroyServiceEngine()
- }
- }
-
- suspend fun destroyServiceEngine() {
- runLock.withLock {
- GlobalState.log("Destroy service engine")
- withContext(Dispatchers.Main) {
- runCatching {
- serviceFlutterEngine?.destroy()
- serviceFlutterEngine = null
- }
- }
- }
- }
-
- private fun startServiceWithEngine() {
+ private fun startServiceWithPref() {
GlobalState.launch {
runLock.withLock {
if (runStateFlow.value != RunState.STOP) {
return@launch
}
- GlobalState.log("Create service engine")
- withContext(Dispatchers.Main) {
- serviceFlutterEngine?.destroy()
- serviceFlutterEngine = FlutterEngine(GlobalState.application)
- serviceFlutterEngine?.plugins?.add(ServicePlugin())
- serviceFlutterEngine?.plugins?.add(AppPlugin())
- serviceFlutterEngine?.plugins?.add(TilePlugin())
- val dartEntrypoint = DartExecutor.DartEntrypoint(
- FlutterInjector.instance().flutterLoader().findAppBundlePath(), "_service"
- )
- serviceFlutterEngine?.dartExecutor?.executeDartEntrypoint(dartEntrypoint)
- }
+ sharedState = GlobalState.application.sharedState
+ setupAndStart()
}
}
}
+ suspend fun syncState() {
+ GlobalState.setCrashlytics(sharedState.crashlytics)
+ Service.updateNotificationParams(
+ NotificationParams(
+ title = sharedState.currentProfileName,
+ stopText = sharedState.stopText,
+ onlyStatisticsProxy = sharedState.onlyStatisticsProxy
+ )
+ )
+ Service.setCrashlytics(sharedState.crashlytics)
+ }
+
+ private suspend fun setupAndStart() {
+ Service.bind()
+ syncState()
+ GlobalState.application.showToast(sharedState.startTip)
+ val initParams = mutableMapOf()
+ 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() {
GlobalState.launch {
runLock.withLock {
if (runStateFlow.value != RunState.STOP) {
return@launch
}
- runStateFlow.tryEmit(RunState.PENDING)
- if (servicePlugin == null) {
- return@launch
- }
- val options = servicePlugin?.handleGetVpnOptions() ?: return@launch
- appPlugin?.prepare(options.enable) {
- runTime = Service.startService(options, runTime)
- runStateFlow.tryEmit(RunState.START)
+ try {
+ runStateFlow.tryEmit(RunState.PENDING)
+ val options = sharedState.vpnOptions ?: return@launch
+ appPlugin?.let {
+ it.prepare(options.enable) {
+ runTime = Service.startService(options, runTime)
+ runStateFlow.tryEmit(RunState.START)
+ }
+ } ?: run {
+ val intent = VpnService.prepare(GlobalState.application)
+ if (intent != null) {
+ return@launch
+ }
+ runTime = Service.startService(options, runTime)
+ runStateFlow.tryEmit(RunState.START)
+ }
+ } finally {
+ if (runStateFlow.value == RunState.PENDING) {
+ runStateFlow.tryEmit(RunState.STOP)
+ }
}
}
}
+ }
+ fun handleStopService() {
+ GlobalState.launch {
+ runLock.withLock {
+ if (runStateFlow.value != RunState.START) {
+ return@launch
+ }
+ try {
+ runStateFlow.tryEmit(RunState.PENDING)
+ runTime = Service.stopService()
+ runStateFlow.tryEmit(RunState.STOP)
+ } finally {
+ if (runStateFlow.value == RunState.PENDING) {
+ runStateFlow.tryEmit(RunState.START)
+ }
+ }
+ }
+ }
}
}
diff --git a/android/app/src/main/kotlin/com/follow/clash/models/State.kt b/android/app/src/main/kotlin/com/follow/clash/models/State.kt
index 0ca5175..35e461b 100644
--- a/android/app/src/main/kotlin/com/follow/clash/models/State.kt
+++ b/android/app/src/main/kotlin/com/follow/clash/models/State.kt
@@ -1,9 +1,22 @@
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 startTip: String = "Starting VPN...",
+ val stopTip: String = "Stopping VPN...",
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,
)
diff --git a/android/app/src/main/kotlin/com/follow/clash/plugins/AppPlugin.kt b/android/app/src/main/kotlin/com/follow/clash/plugins/AppPlugin.kt
index 730499e..cf69752 100644
--- a/android/app/src/main/kotlin/com/follow/clash/plugins/AppPlugin.kt
+++ b/android/app/src/main/kotlin/com/follow/clash/plugins/AppPlugin.kt
@@ -9,7 +9,6 @@ import android.content.pm.ComponentInfo
import android.content.pm.PackageManager
import android.net.VpnService
import android.os.Build
-import android.widget.Toast
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
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.getPackageIconPath
import com.follow.clash.models.Package
+import com.follow.clash.showToast
import com.google.gson.Gson
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.plugins.FlutterPlugin
@@ -193,7 +193,7 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
}
private fun tip(message: String?) {
- Toast.makeText(GlobalState.application, message, Toast.LENGTH_LONG).show()
+ GlobalState.application.showToast(message)
}
@Suppress("DEPRECATION")
diff --git a/android/app/src/main/kotlin/com/follow/clash/plugins/ServicePlugin.kt b/android/app/src/main/kotlin/com/follow/clash/plugins/ServicePlugin.kt
index 2b0d1ff..00d8bd3 100644
--- a/android/app/src/main/kotlin/com/follow/clash/plugins/ServicePlugin.kt
+++ b/android/app/src/main/kotlin/com/follow/clash/plugins/ServicePlugin.kt
@@ -3,13 +3,9 @@ package com.follow.clash.plugins
import com.follow.clash.RunState
import com.follow.clash.Service
import com.follow.clash.State
-import com.follow.clash.awaitResult
import com.follow.clash.common.Components
-import com.follow.clash.common.GlobalState
import com.follow.clash.invokeMethodOnMainThread
-import com.follow.clash.models.AppState
-import com.follow.clash.service.models.NotificationParams
-import com.follow.clash.service.models.VpnOptions
+import com.follow.clash.models.SharedState
import com.google.gson.Gson
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.MethodCall
@@ -38,7 +34,7 @@ class ServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) = when (call.method) {
"init" -> {
- handleInit(call, result)
+ handleInit(result)
}
"shutdown" -> {
@@ -94,11 +90,6 @@ class ServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
result.success(true)
}
- suspend fun handleGetVpnOptions(): VpnOptions? {
- val res = flutterMethodChannel.awaitResult("getVpnOptions", null)
- return Gson().fromJson(res, VpnOptions::class.java)
- }
-
val semaphore = Semaphore(10)
fun handleSendEvent(value: String?) {
@@ -116,31 +107,19 @@ class ServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
private fun handleSyncState(call: MethodCall, result: MethodChannel.Result) {
val data = call.arguments()!!
- val params = Gson().fromJson(data, AppState::class.java)
- GlobalState.setCrashlytics(params.crashlytics)
+ State.sharedState = Gson().fromJson(data, SharedState::class.java)
launch {
- Service.updateNotificationParams(
- NotificationParams(
- title = params.currentProfileName,
- stopText = params.stopText,
- onlyStatisticsProxy = params.onlyStatisticsProxy
- )
- )
- Service.setCrashlytics(params.crashlytics)
+ State.syncState()
result.success("")
}
}
- fun handleInit(call: MethodCall, result: MethodChannel.Result) {
+
+ fun handleInit(result: MethodChannel.Result) {
Service.bind()
launch {
- val needSetEventListener = call.arguments() ?: false
- when (needSetEventListener) {
- true -> Service.setEventListener {
- handleSendEvent(it)
- }
-
- false -> Service.setEventListener(null)
+ Service.setEventListener {
+ handleSendEvent(it)
}.onSuccess {
result.success("")
}.onFailure {
diff --git a/android/build.gradle.kts b/android/build.gradle.kts
index 8f89645..dbee657 100644
--- a/android/build.gradle.kts
+++ b/android/build.gradle.kts
@@ -1,19 +1,10 @@
-buildscript {
- dependencies {
- classpath(libs.build.kotlin)
- }
-}
-
-plugins {
- id("com.android.library") apply false
-}
-
allprojects {
repositories {
google()
mavenCentral()
}
}
+
val newBuildDir: Directory =
rootProject.layout.buildDirectory
.dir("../../build")
@@ -31,4 +22,3 @@ subprojects {
tasks.register("clean") {
delete(rootProject.layout.buildDirectory)
}
-
diff --git a/android/core/src/main/cpp/core.cpp b/android/core/src/main/cpp/core.cpp
index 5c1251c..878d20c 100644
--- a/android/core/src/main/cpp/core.cpp
+++ b/android/core/src/main/cpp/core.cpp
@@ -70,6 +70,14 @@ Java_com_follow_clash_core_Core_suspended(JNIEnv *env, jobject thiz, jboolean su
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_resolve_process;
@@ -99,12 +107,12 @@ call_tun_interface_resolve_process_impl(void *tun_interface, const int protocol,
const int uid) {
ATTACH_JNI();
const auto packageName = reinterpret_cast(env->CallObjectMethod(
- static_cast(tun_interface),
- m_tun_interface_resolve_process,
- protocol,
- new_string(source),
- new_string(target),
- uid));
+ static_cast(tun_interface),
+ m_tun_interface_resolve_process,
+ protocol,
+ new_string(source),
+ new_string(target),
+ uid));
return get_string(packageName);
}
@@ -191,4 +199,10 @@ extern "C"
JNIEXPORT void JNICALL
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
\ No newline at end of file
diff --git a/android/core/src/main/java/com/follow/clash/core/Core.kt b/android/core/src/main/java/com/follow/clash/core/Core.kt
index 830f080..bbc4e70 100644
--- a/android/core/src/main/java/com/follow/clash/core/Core.kt
+++ b/android/core/src/main/java/com/follow/clash/core/Core.kt
@@ -102,6 +102,28 @@ data object Core {
}
}
+ fun quickSetup(
+ initParamsString: String,
+ setupParamsString: String,
+ cb: (result: String?) -> Unit,
+ ) {
+ quickSetup(
+ initParamsString,
+ setupParamsString,
+ object : InvokeInterface {
+ override fun onResult(result: String?) {
+ cb(result)
+ }
+ },
+ )
+ }
+
+ private external fun quickSetup(
+ initParamsString: String,
+ setupParamsString: String,
+ cb: InvokeInterface
+ )
+
external fun stopTun()
external fun getTraffic(onlyStatisticsProxy: Boolean): String
diff --git a/android/service/src/main/aidl/com/follow/clash/service/IRemoteInterface.aidl b/android/service/src/main/aidl/com/follow/clash/service/IRemoteInterface.aidl
index a9cf775..4eac0b7 100644
--- a/android/service/src/main/aidl/com/follow/clash/service/IRemoteInterface.aidl
+++ b/android/service/src/main/aidl/com/follow/clash/service/IRemoteInterface.aidl
@@ -4,11 +4,13 @@ package com.follow.clash.service;
import com.follow.clash.service.ICallbackInterface;
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.NotificationParams;
interface IRemoteInterface {
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 startService(in VpnOptions options, in long runTime, in IResultInterface result);
void stopService(in IResultInterface result);
diff --git a/android/service/src/main/aidl/com/follow/clash/service/IVoidInterface.aidl b/android/service/src/main/aidl/com/follow/clash/service/IVoidInterface.aidl
new file mode 100644
index 0000000..28db4c3
--- /dev/null
+++ b/android/service/src/main/aidl/com/follow/clash/service/IVoidInterface.aidl
@@ -0,0 +1,6 @@
+// IVoidInterface.aidl
+package com.follow.clash.service;
+
+interface IVoidInterface {
+ oneway void invoke();
+}
\ No newline at end of file
diff --git a/android/service/src/main/java/com/follow/clash/service/RemoteService.kt b/android/service/src/main/java/com/follow/clash/service/RemoteService.kt
index 5fbb6e1..54e608a 100644
--- a/android/service/src/main/java/com/follow/clash/service/RemoteService.kt
+++ b/android/service/src/main/java/com/follow/clash/service/RemoteService.kt
@@ -98,6 +98,35 @@ class RemoteService : Service(),
}
}
+ 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?) {
State.notificationParamsFlow.tryEmit(params)
}
@@ -108,6 +137,7 @@ class RemoteService : Service(),
runtime: Long,
result: IResultInterface,
) {
+ GlobalState.log("remote startService")
State.options = options
handleStartService(runtime, result)
}
diff --git a/android/service/src/main/java/com/follow/clash/service/VpnService.kt b/android/service/src/main/java/com/follow/clash/service/VpnService.kt
index 883064a..f576702 100644
--- a/android/service/src/main/java/com/follow/clash/service/VpnService.kt
+++ b/android/service/src/main/java/com/follow/clash/service/VpnService.kt
@@ -187,7 +187,7 @@ class VpnService : SystemVpnService(), IBaseService,
addDnsServer(DNS6)
}
setMtu(9000)
- options.accessControl.let { accessControl ->
+ options.accessControlProps.let { accessControl ->
if (accessControl.enable) {
when (accessControl.mode) {
AccessControlMode.ACCEPT_SELECTED -> {
diff --git a/android/service/src/main/java/com/follow/clash/service/models/VpnOptions.kt b/android/service/src/main/java/com/follow/clash/service/models/VpnOptions.kt
index 7d18890..0e2b4ac 100644
--- a/android/service/src/main/java/com/follow/clash/service/models/VpnOptions.kt
+++ b/android/service/src/main/java/com/follow/clash/service/models/VpnOptions.kt
@@ -6,7 +6,7 @@ import kotlinx.parcelize.Parcelize
import java.net.InetAddress
@Parcelize
-data class AccessControl(
+data class AccessControlProps(
val enable: Boolean,
val mode: AccessControlMode,
val acceptList: List,
@@ -19,7 +19,7 @@ data class VpnOptions(
val port: Int,
val ipv6: Boolean,
val dnsHijacking: Boolean,
- val accessControl: AccessControl,
+ val accessControlProps: AccessControlProps,
val allowBypass: Boolean,
val systemProxy: Boolean,
val bypassDomain: List,
diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts
index 1fc9b74..dd51c86 100644
--- a/android/settings.gradle.kts
+++ b/android/settings.gradle.kts
@@ -1,11 +1,12 @@
pluginManagement {
- val flutterSdkPath = run {
- val properties = java.util.Properties()
- file("local.properties").inputStream().use { properties.load(it) }
- val flutterSdkPath = properties.getProperty("flutter.sdk")
- require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
- flutterSdkPath
- }
+ val flutterSdkPath =
+ run {
+ val properties = java.util.Properties()
+ file("local.properties").inputStream().use { properties.load(it) }
+ val flutterSdkPath = properties.getProperty("flutter.sdk")
+ require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
+ flutterSdkPath
+ }
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
diff --git a/arb/intl_en.arb b/arb/intl_en.arb
index 8c93cdf..84dbe2d 100644
--- a/arb/intl_en.arb
+++ b/arb/intl_en.arb
@@ -129,14 +129,8 @@
"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.",
"tip": "tip",
- "backupAndRecovery": "Backup and Recovery",
- "backupAndRecoveryDesc": "Sync data via WebDAV or file",
"account": "Account",
"backup": "Backup",
- "recovery": "Recovery",
- "recoveryProfiles": "Only recovery profiles",
- "recoveryAll": "Recovery all data",
- "recoverySuccess": "Recovery success",
"backupSuccess": "Backup success",
"noInfo": "No info",
"pleaseBindWebDAV": "Please bind WebDAV",
@@ -219,9 +213,7 @@
"local": "Local",
"remote": "Remote",
"remoteBackupDesc": "Backup local data to WebDAV",
- "remoteRecoveryDesc": "Recovery data from WebDAV",
"localBackupDesc": "Backup local data to local",
- "localRecoveryDesc": "Recovery data from file",
"mode": "Mode",
"time": "Time",
"source": "Source",
@@ -381,9 +373,9 @@
"systemApp": "System APP",
"noNetworkApp": "No network APP",
"contactMe": "Contact me",
- "recoveryStrategy": "Recovery strategy",
- "recoveryStrategy_override": "Override",
- "recoveryStrategy_compatible": "Compatible",
+ "restoreStrategy": "Restore strategy",
+ "restoreStrategy_override": "Override",
+ "restoreStrategy_compatible": "Compatible",
"logsTest": "Logs test",
"emptyTip": "{label} cannot be empty",
"urlTip": "{label} must be a url",
@@ -466,5 +458,23 @@
"vpnConfigChangeDetected": "VPN configuration change detected",
"restart": "Restart",
"speedStatistics": "Speed statistics",
- "resetPageChangesTip": "The current page has changes. Are you sure you want to reset?"
+ "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"
}
\ No newline at end of file
diff --git a/arb/intl_ja.arb b/arb/intl_ja.arb
index f9b7ff4..7df092b 100644
--- a/arb/intl_ja.arb
+++ b/arb/intl_ja.arb
@@ -129,14 +129,8 @@
"compatibleDesc": "有効化すると一部機能を失いますが、Clashの完全サポートを獲得",
"notSelectedTip": "現在のプロキシグループは選択できません",
"tip": "ヒント",
- "backupAndRecovery": "バックアップと復元",
- "backupAndRecoveryDesc": "WebDAVまたはファイルでデータを同期",
"account": "アカウント",
"backup": "バックアップ",
- "recovery": "復元",
- "recoveryProfiles": "プロファイルのみ復元",
- "recoveryAll": "全データ復元",
- "recoverySuccess": "復元成功",
"backupSuccess": "バックアップ成功",
"noInfo": "情報なし",
"pleaseBindWebDAV": "WebDAVをバインドしてください",
@@ -219,9 +213,7 @@
"local": "ローカル",
"remote": "リモート",
"remoteBackupDesc": "WebDAVにデータをバックアップ",
- "remoteRecoveryDesc": "WebDAVからデータを復元",
"localBackupDesc": "ローカルにデータをバックアップ",
- "localRecoveryDesc": "ファイルからデータを復元",
"mode": "モード",
"time": "時間",
"source": "ソース",
@@ -382,9 +374,9 @@
"systemApp": "システムアプリ",
"noNetworkApp": "ネットワークなしアプリ",
"contactMe": "連絡する",
- "recoveryStrategy": "リカバリー戦略",
- "recoveryStrategy_override": "オーバーライド",
- "recoveryStrategy_compatible": "互換性",
+ "restoreStrategy": "復元ストラテジー",
+ "restoreStrategy_override": "上書き",
+ "restoreStrategy_compatible": "互換",
"logsTest": "ログテスト",
"emptyTip": "{label}は空欄にできません",
"urlTip": "{label}はURLである必要があります",
@@ -467,5 +459,23 @@
"vpnConfigChangeDetected": "VPN設定の変更が検出されました",
"restart": "再起動",
"speedStatistics": "速度統計",
- "resetPageChangesTip": "現在のページに変更があります。リセットしてもよろしいですか?"
+ "resetPageChangesTip": "現在のページに変更があります。リセットしてもよろしいですか?",
+ "overwriteTypeCustom": "カスタム",
+ "overwriteTypeCustomDesc": "カスタムモード、プロキシグループとルールを完全にカスタマイズ可能",
+ "unknownNetworkError": "不明なネットワークエラー",
+ "networkRequestException": "ネットワーク要求例外、後でもう一度試してください。",
+ "restoreException": "復元例外",
+ "networkException": "ネットワーク例外、接続を確認してもう一度お試しください",
+ "invalidBackupFile": "無効なバックアップファイル",
+ "pruneCache": "キャッシュの削除",
+ "backupAndRestore": "バックアップと復元",
+ "backupAndRestoreDesc": "WebDAVまたはファイルを介してデータを同期する",
+ "restore": "復元",
+ "restoreSuccess": "復元に成功しました",
+ "restoreFromWebDAVDesc": "WebDAVを介してデータを復元する",
+ "restoreFromFileDesc": "ファイルを介してデータを復元する",
+ "restoreOnlyConfig": "設定ファイルのみを復元する",
+ "restoreAllData": "すべてのデータを復元する",
+ "addProfile": "プロファイルを追加",
+ "delayTest": "遅延テスト"
}
\ No newline at end of file
diff --git a/arb/intl_ru.arb b/arb/intl_ru.arb
index 433ed57..ff62742 100644
--- a/arb/intl_ru.arb
+++ b/arb/intl_ru.arb
@@ -382,9 +382,9 @@
"systemApp": "Системное приложение",
"noNetworkApp": "Приложение без сети",
"contactMe": "Свяжитесь со мной",
- "recoveryStrategy": "Стратегия восстановления",
- "recoveryStrategy_override": "Переопределение",
- "recoveryStrategy_compatible": "Совместимый",
+ "restoreStrategy": "Стратегия восстановления",
+ "restoreStrategy_override": "Перезаписать",
+ "restoreStrategy_compatible": "Совместимый",
"logsTest": "Тест журналов",
"emptyTip": "{label} не может быть пустым",
"urlTip": "{label} должен быть URL",
@@ -467,5 +467,23 @@
"vpnConfigChangeDetected": "Обнаружено изменение конфигурации VPN",
"restart": "Перезапустить",
"speedStatistics": "Статистика скорости",
- "resetPageChangesTip": "На текущей странице есть изменения. Вы уверены, что хотите сбросить?"
+ "resetPageChangesTip": "На текущей странице есть изменения. Вы уверены, что хотите сбросить?",
+ "overwriteTypeCustom": "Пользовательский",
+ "overwriteTypeCustomDesc": "Пользовательский режим, полная настройка групп прокси и правил",
+ "unknownNetworkError": "Неизвестная сетевая ошибка",
+ "networkRequestException": "Исключение сетевого запроса, пожалуйста, попробуйте позже.",
+ "restoreException": "Ошибка восстановления",
+ "networkException": "Ошибка сети, проверьте соединение и попробуйте еще раз",
+ "invalidBackupFile": "Неверный файл резервной копии",
+ "pruneCache": "Очистить кэш",
+ "backupAndRestore": "Резервное копирование и восстановление",
+ "backupAndRestoreDesc": "Синхронизация данных через WebDAV или файлы",
+ "restore": "Восстановить",
+ "restoreSuccess": "Восстановление успешно",
+ "restoreFromWebDAVDesc": "Восстановить данные через WebDAV",
+ "restoreFromFileDesc": "Восстановить данные из файла",
+ "restoreOnlyConfig": "Восстановить только файлы конфигурации",
+ "restoreAllData": "Восстановить все данные",
+ "addProfile": "Добавить профиль",
+ "delayTest": "Тест задержки"
}
\ No newline at end of file
diff --git a/arb/intl_zh_CN.arb b/arb/intl_zh_CN.arb
index abba2bc..3e3359b 100644
--- a/arb/intl_zh_CN.arb
+++ b/arb/intl_zh_CN.arb
@@ -129,14 +129,8 @@
"compatibleDesc": "开启将失去部分应用能力,获得全量的Clash的支持",
"notSelectedTip": "当前代理组无法选中",
"tip": "提示",
- "backupAndRecovery": "备份与恢复",
- "backupAndRecoveryDesc": "通过WebDAV或者文件同步数据",
"account": "账号",
"backup": "备份",
- "recovery": "恢复",
- "recoveryProfiles": "仅恢复配置文件",
- "recoveryAll": "恢复所有数据",
- "recoverySuccess": "恢复成功",
"backupSuccess": "备份成功",
"noInfo": "暂无信息",
"pleaseBindWebDAV": "请绑定WebDAV",
@@ -219,9 +213,7 @@
"local": "本地",
"remote": "远程",
"remoteBackupDesc": "备份数据到WebDAV",
- "remoteRecoveryDesc": "通过WebDAV恢复数据",
"localBackupDesc": "备份数据到本地",
- "localRecoveryDesc": "通过文件恢复数据",
"mode": "模式",
"time": "时间",
"source": "来源",
@@ -382,9 +374,9 @@
"systemApp": "系统应用",
"noNetworkApp": "无网络应用",
"contactMe": "联系我",
- "recoveryStrategy": "恢复策略",
- "recoveryStrategy_override": "覆盖",
- "recoveryStrategy_compatible": "兼容",
+ "restoreStrategy": "恢复策略",
+ "restoreStrategy_override": "覆盖",
+ "restoreStrategy_compatible": "兼容",
"logsTest": "日志测试",
"emptyTip": "{label}不能为空",
"urlTip": "{label}必须为URL",
@@ -467,5 +459,23 @@
"vpnConfigChangeDetected": "检测到VPN相关配置改动",
"restart": "重启",
"speedStatistics": "网速统计",
- "resetPageChangesTip": "当前页面存在更改,确定重置吗?"
+ "resetPageChangesTip": "当前页面存在更改,确定重置吗?",
+ "overwriteTypeCustom": "自定义",
+ "overwriteTypeCustomDesc": "自定义模式,支持完全自定义修改代理组以及规则",
+ "unknownNetworkError": "未知网络错误",
+ "networkRequestException": "网络请求异常,请稍后再试。",
+ "restoreException": "恢复异常",
+ "networkException": "网络异常,请检查连接后重试",
+ "invalidBackupFile": "无效备份文件",
+ "pruneCache": "修剪缓存",
+ "backupAndRestore": "备份与恢复",
+ "backupAndRestoreDesc": "通过WebDAV或者文件同步数据",
+ "restore": "恢复",
+ "restoreSuccess": "恢复成功",
+ "restoreFromWebDAVDesc": "通过WebDAV恢复数据",
+ "restoreFromFileDesc": "通过文件恢复数据",
+ "restoreOnlyConfig": "仅恢复配置文件",
+ "restoreAllData": "恢复所有数据",
+ "addProfile": "添加配置",
+ "delayTest": "延迟测试"
}
diff --git a/build.yaml b/build.yaml
index 5098650..efbbe97 100644
--- a/build.yaml
+++ b/build.yaml
@@ -6,6 +6,7 @@ targets:
build_extensions:
'^lib/models/{{}}.dart': 'lib/models/generated/{{}}.g.dart'
'^lib/providers/{{}}.dart': 'lib/providers/generated/{{}}.g.dart'
+ '^lib/database/{{}}.dart': 'lib/database/generated/{{}}.g.dart'
freezed:
options:
build_extensions:
diff --git a/core/Clash.Meta b/core/Clash.Meta
index e0cf7fb..c27f82f 160000
--- a/core/Clash.Meta
+++ b/core/Clash.Meta
@@ -1 +1 @@
-Subproject commit e0cf7fb4e641741592e87890be8427594932535e
+Subproject commit c27f82fbdf2a79b1b9ed0dd46d7a50829b168bc4
diff --git a/core/common.go b/core/common.go
index ca73f6f..410c45b 100644
--- a/core/common.go
+++ b/core/common.go
@@ -37,12 +37,6 @@ var (
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 {
eps := make(map[string]cp.Provider)
for n, p := range tunnel.Providers() {
@@ -241,25 +235,8 @@ func updateConfig(params *UpdateParams) {
updateListeners()
}
-func parseWithPath(path string) (*config.Config, error) {
- buf, err := readFile(path)
- if err != nil {
- return nil, err
- }
- rawConfig := config.DefaultRawConfig()
- err = UnmarshalJson(buf, rawConfig)
- if err != nil {
- return nil, err
- }
- parseRawConfig, err := config.ParseRawConfig(rawConfig)
- if err != nil {
- return nil, err
- }
-
- return parseRawConfig, nil
-}
-
-func setupConfig(params *SetupParams) error {
+func applyConfig(params *SetupParams) error {
+ runtime.GC()
runLock.Lock()
defer runLock.Unlock()
var err error
@@ -271,7 +248,6 @@ func setupConfig(params *SetupParams) error {
hub.ApplyConfig(currentConfig)
patchSelectGroup(params.SelectedMap)
updateListeners()
- runtime.GC()
return err
}
diff --git a/core/constant.go b/core/constant.go
index 1cd68f5..af79f1e 100644
--- a/core/constant.go
+++ b/core/constant.go
@@ -66,6 +66,11 @@ type ExternalProvider struct {
SubscriptionInfo *provider.SubscriptionInfo `json:"subscription-info"`
}
+type ProxiesData struct {
+ Proxies map[string]constant.Proxy `json:"proxies"`
+ All []string `json:"all"`
+}
+
const (
messageMethod Method = "message"
initClashMethod Method = "initClash"
diff --git a/core/go.mod b/core/go.mod
index 76079a4..9e7597d 100644
--- a/core/go.mod
+++ b/core/go.mod
@@ -10,6 +10,7 @@ require (
)
require (
+ filippo.io/edwards25519 v1.1.0 // indirect
github.com/RyuaNerin/go-krypto v1.3.0 // indirect
github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 // indirect
github.com/ajg/form v1.5.1 // indirect
@@ -18,18 +19,14 @@ require (
github.com/buger/jsonparser v1.1.1 // indirect
github.com/coreos/go-iptables v0.8.0 // indirect
github.com/dlclark/regexp2 v1.11.5 // indirect
- github.com/ebitengine/purego v0.9.1 // indirect
- github.com/enfein/mieru/v3 v3.22.1 // indirect
+ github.com/enfein/mieru/v3 v3.26.2 // indirect
github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358 // indirect
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 // indirect
github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1 // indirect
github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010 // indirect
github.com/fsnotify/fsnotify v1.9.0 // 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-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/gobwas/httphead v0.1.0 // indirect
github.com/gobwas/pool v0.2.1 // indirect
github.com/gobwas/ws v1.4.0 // indirect
@@ -37,7 +34,7 @@ require (
github.com/golang/snappy v1.0.0 // indirect
github.com/google/btree v1.1.3 // 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/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905 // indirect
github.com/josharian/native v1.1.0 // indirect
github.com/klauspost/compress v1.17.9 // indirect
@@ -52,37 +49,42 @@ require (
github.com/metacubex/bbolt v0.0.0-20250725135710-010dbbbb7a5b // indirect
github.com/metacubex/blake3 v0.1.0 // 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/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 // indirect
- github.com/metacubex/gvisor v0.0.0-20250919004547-6122b699a301 // indirect
- github.com/metacubex/kcp-go v0.0.0-20251105084629-8c93f4bf37be // 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/quic-go v0.55.1-0.20251024060151-bd465f127128 // 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/restls-client-go v0.1.7 // indirect
github.com/metacubex/sing v0.5.6 // indirect
github.com/metacubex/sing-mux v0.3.4 // indirect
- github.com/metacubex/sing-quic v0.0.0-20251004051927-c45ee18473bb // 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-shadowsocks2 v0.2.7 // indirect
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 // indirect
- github.com/metacubex/sing-tun v0.4.9 // indirect
+ github.com/metacubex/sing-tun v0.4.11 // indirect
github.com/metacubex/sing-vmess v0.2.4 // indirect
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f // indirect
- github.com/metacubex/smux v0.0.0-20250922175018-15c9a6a78719 // indirect
- github.com/metacubex/tfo-go v0.0.0-20251024101424-368b42b59148 // indirect
- github.com/metacubex/utls v1.8.3 // indirect
+ github.com/metacubex/smux v0.0.0-20260105030934-d0c8756d3141 // indirect
+ github.com/metacubex/tfo-go v0.0.0-20251130171125-413e892ac443 // 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/yamux v0.0.0-20250918083631-dd5f17c0be49 // indirect
github.com/miekg/dns v1.1.63 // indirect
github.com/mroth/weightedrand/v2 v2.1.0 // 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/oschwald/maxminddb-golang v1.12.0 // indirect
github.com/pierrec/lz4/v4 v4.1.14 // 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/samber/lo v1.52.0 // indirect
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b // indirect
@@ -103,7 +105,7 @@ require (
golang.org/x/net v0.35.0 // indirect
golang.org/x/sys v0.30.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
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
diff --git a/core/go.sum b/core/go.sum
index deb1b71..f670c13 100644
--- a/core/go.sum
+++ b/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/go.mod h1:9R9TU936laAIqAmjcHo/LsaXYOZlymudOAxjaBf62UM=
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/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
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/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
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/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
-github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A=
-github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
-github.com/enfein/mieru/v3 v3.22.1 h1:/XGYYXpEhEJlxosmtbpEJkhtRLHB8IToG7LB8kU2ZDY=
-github.com/enfein/mieru/v3 v3.22.1/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM=
+github.com/enfein/mieru/v3 v3.26.2 h1:U/2XJc+3vrJD9r815FoFdwToQFEcqSOzzzWIPPhjfEU=
+github.com/enfein/mieru/v3 v3.26.2/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/go.mod h1:hkIFzoiIPZYxdFOOLyDho59b7SrDfo+w3h+yWdlg45I=
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 h1:8j2RH289RJplhA6WfdaPqzg1MjH2K8wX5e0uhAxrw2g=
@@ -39,15 +36,8 @@ 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/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk=
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.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
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/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
@@ -57,17 +47,15 @@ github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakr
github.com/gofrs/uuid/v5 v5.4.0 h1:EfbpCTjqMuGyq5ZJwxqzn3Cbr2d0rUZU7v5ycAk/e/0=
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.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/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
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/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
-github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k=
+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/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/go.mod h1:VvGYjkZoJyKqlmT1yzakUs4mfKMNB0XdODP0+rdml6k=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
@@ -98,47 +86,62 @@ github.com/metacubex/blake3 v0.1.0 h1:KGnjh/56REO7U+cgZA8dnBhxdP7jByrG7hTP+bu6cq
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/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/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/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88=
-github.com/metacubex/gvisor v0.0.0-20250919004547-6122b699a301 h1:N5GExQJqYAH3gOCshpp2u/J3CtNYzMctmlb0xK9wtbQ=
-github.com/metacubex/gvisor v0.0.0-20250919004547-6122b699a301/go.mod h1:8LpS0IJW1VmWzUm3ylb0e2SK5QDm5lO/2qwWLZgRpBU=
-github.com/metacubex/kcp-go v0.0.0-20251105084629-8c93f4bf37be h1:Y7SigZIqfv/+RIA/D7R6EbB9p+brPRoGOM6zobSmRIM=
-github.com/metacubex/kcp-go v0.0.0-20251105084629-8c93f4bf37be/go.mod h1:HIJZW4QMhbBqXuqC1ly6Hn0TEYT2SzRw58ns1yGhXTs=
+github.com/metacubex/gvisor v0.0.0-20251227095601-261ec1326fe8 h1:hUL81H0Ic/XIDkvtn9M1pmfDdfid7JzYQToY4Ps1TvQ=
+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/go.mod h1:RjRNb4G52yAgfR+Oe/kp9G4PJJ97Fnj89eY1BFO3YyA=
-github.com/metacubex/quic-go v0.55.1-0.20251024060151-bd465f127128 h1:I1uvJl206/HbkzEAZpLgGkZgUveOZb+P+6oTUj7dN+o=
-github.com/metacubex/quic-go v0.55.1-0.20251024060151-bd465f127128/go.mod h1:1lktQFtCD17FZliVypbrDHwbsFSsmz2xz2TRXydvB5c=
+github.com/metacubex/qpack v0.6.0 h1:YqClGIMOpiRYLjV1qOs483Od08MdPgRnHjt90FuaAKw=
+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/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFqSPyj2cxY=
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/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.6/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w=
github.com/metacubex/sing-mux v0.3.4 h1:tf4r27CIkzaxq9kBlAXQkgMXq2HPp5Mta60Kb4RCZF0=
github.com/metacubex/sing-mux v0.3.4/go.mod h1:SEJfAuykNj/ozbPqngEYqyggwSr81+L7Nu09NRD5mh4=
-github.com/metacubex/sing-quic v0.0.0-20251004051927-c45ee18473bb h1:gxrJmnxuEAel+kh3V7ntqkHjURif0xKDu76nzr/BF5Y=
-github.com/metacubex/sing-quic v0.0.0-20251004051927-c45ee18473bb/go.mod h1:JK4+PYUKps6pnlicKjsSUAjAcvIUjhorIjdNZGg930M=
+github.com/metacubex/sing-quic v0.0.0-20260112044712-65d17608159e h1:MLxp42z9Jd6LtY2suyawnl24oNzIsFxWc15bNeDIGxA=
+github.com/metacubex/sing-quic v0.0.0-20260112044712-65d17608159e/go.mod h1:+lgKTd52xAarGtqugALISShyw4KxnoEpYe2u0zJh26w=
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-shadowsocks2 v0.2.7 h1:hSuuc0YpsfiqYqt1o+fP4m34BQz4e6wVj3PPBVhor3A=
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/go.mod h1:mbfboaXauKJNIHJYxQRa+NJs4JU9NZfkA+I33dS2+9E=
-github.com/metacubex/sing-tun v0.4.9 h1:jY0Yyt8nnN3yQRN/jTxgqNCmGi1dsFdxdIi7pQUlVVU=
-github.com/metacubex/sing-tun v0.4.9/go.mod h1:L/TjQY5JEGy8nvsuYmy/XgMFMCPiF0+AWSFCYfS6r9w=
+github.com/metacubex/sing-tun v0.4.11 h1:NG5zpvYPbBXf+9GSUmDaGCDwl3hZXV677tbRAw0QtCM=
+github.com/metacubex/sing-tun v0.4.11/go.mod h1:L/TjQY5JEGy8nvsuYmy/XgMFMCPiF0+AWSFCYfS6r9w=
github.com/metacubex/sing-vmess v0.2.4 h1:Tx6AGgCiEf400E/xyDuYyafsel6sGbR8oF7RkAaus6I=
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/go.mod h1:jpAkVLPnCpGSfNyVmj6Cq4YbuZsFepm/Dc+9BAOcR80=
-github.com/metacubex/smux v0.0.0-20250922175018-15c9a6a78719 h1:T6qCCfolRDAVJKeaPW/mXwNLjnlo65AYN7WS2jrBNaM=
-github.com/metacubex/smux v0.0.0-20250922175018-15c9a6a78719/go.mod h1:4bPD8HWx9jPJ9aE4uadgyN7D1/Wz3KmPy+vale8sKLE=
-github.com/metacubex/tfo-go v0.0.0-20251024101424-368b42b59148 h1:Zd0QqciLIhv9MKbGKTPEgN8WUFsgQGA1WJBy6spEnVU=
-github.com/metacubex/tfo-go v0.0.0-20251024101424-368b42b59148/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
-github.com/metacubex/utls v1.8.3 h1:0m/yCxm3SK6kWve2lKiFb1pue1wHitJ8sQQD4Ikqde4=
-github.com/metacubex/utls v1.8.3/go.mod h1:kncGGVhFaoGn5M3pFe3SXhZCzsbCJayNOH4UEqTKTko=
+github.com/metacubex/smux v0.0.0-20260105030934-d0c8756d3141 h1:DK2l6m2Fc85H2BhiAPgbJygiWhesPlfGmF+9Vw6ARdk=
+github.com/metacubex/smux v0.0.0-20260105030934-d0c8756d3141/go.mod h1:/yI4OiGOSn0SURhZdJF3CbtPg3nwK700bG8TZLMBvAg=
+github.com/metacubex/tfo-go v0.0.0-20251130171125-413e892ac443 h1:H6TnfM12tOoTizYE/qBHH3nEuibIelmHI+BVSxVJr8o=
+github.com/metacubex/tfo-go v0.0.0-20251130171125-413e892ac443/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
+github.com/metacubex/tls v0.1.1 h1:BEcZrsPTTfNf4sKZ02EbZodv4UIj7fgHWa1Eqo12Bc0=
+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/go.mod h1:oPGcV994OGJedmmxrcK9+ni7jUEMGhR+uVQAdaduIP4=
github.com/metacubex/yamux v0.0.0-20250918083631-dd5f17c0be49 h1:lhlqpYHopuTLx9xQt22kSA9HtnyTDmk5XjjQVCGHe2E=
@@ -149,9 +152,6 @@ github.com/mroth/weightedrand/v2 v2.1.0 h1:o1ascnB1CIVzsqlfArQQjeMy1U0NcIbBO5rfd
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/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/low v0.1.21 h1:Tr2GNu4N/+rGRYdOsEHOE89cxUIaDViZbVmKz29uKGo=
github.com/openacid/low v0.1.21/go.mod h1:q+MsKI6Pz2xsCkzV4BLj7NR5M4EX0sGz5AqotpZDVh0=
@@ -164,10 +164,6 @@ 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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-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/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw=
@@ -181,16 +177,9 @@ 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/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
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.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.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-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.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
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=
@@ -234,22 +223,20 @@ 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-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-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-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.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.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/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
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.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
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.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
+golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4=
+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.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
diff --git a/core/hub.go b/core/hub.go
index fd973da..2f721a2 100644
--- a/core/hub.go
+++ b/core/hub.go
@@ -1,6 +1,7 @@
package main
import (
+ "cmp"
"context"
"encoding/json"
"github.com/metacubex/mihomo/adapter"
@@ -19,11 +20,11 @@ import (
"github.com/metacubex/mihomo/log"
"github.com/metacubex/mihomo/tunnel"
"github.com/metacubex/mihomo/tunnel/statistic"
+ "golang.org/x/exp/slices"
"net"
"os"
"runtime"
"runtime/debug"
- "sort"
"strconv"
"time"
)
@@ -43,10 +44,8 @@ func handleInitClash(paramsString string) bool {
return false
}
version = params.Version
- if !isInit {
- constant.SetHomeDir(params.HomeDir)
- isInit = true
- }
+ constant.SetHomeDir(params.HomeDir)
+ isInit = true
return isInit
}
@@ -97,10 +96,52 @@ func handleValidateConfig(path string) string {
return ""
}
-func handleGetProxies() map[string]constant.Proxy {
+func handleGetProxies() ProxiesData {
runLock.Lock()
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)) {
@@ -143,7 +184,7 @@ func handleChangeProxy(data string, fn func(string string)) {
}
func handleGetTraffic(onlyStatisticsProxy bool) string {
- up, down := statistic.DefaultManager.Current(onlyStatisticsProxy)
+ up, down := statistic.DefaultManager.NowTraffic(onlyStatisticsProxy)
traffic := map[string]int64{
"up": up,
"down": down,
@@ -157,7 +198,7 @@ func handleGetTraffic(onlyStatisticsProxy bool) string {
}
func handleGetTotalTraffic(onlyStatisticsProxy bool) string {
- up, down := statistic.DefaultManager.Total(onlyStatisticsProxy)
+ up, down := statistic.DefaultManager.TotalTraffic(onlyStatisticsProxy)
traffic := map[string]int64{
"up": up,
"down": down,
@@ -287,7 +328,9 @@ func handleGetExternalProviders() string {
}
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)
if err != nil {
return ""
@@ -489,14 +532,17 @@ func handleDelFile(path string, result ActionResult) {
}
func handleSetupConfig(bytes []byte) string {
+ if !isInit {
+ return "not initialized"
+ }
var params = defaultSetupParams()
err := UnmarshalJson(bytes, params)
if err != nil {
log.Errorln("unmarshalRawConfig error %v", err)
- _ = setupConfig(defaultSetupParams())
+ _ = applyConfig(defaultSetupParams())
return err.Error()
}
- err = setupConfig(params)
+ err = applyConfig(params)
if err != nil {
return err.Error()
}
diff --git a/core/lib.go b/core/lib.go
index d4fe45d..61d0089 100644
--- a/core/lib.go
+++ b/core/lib.go
@@ -201,9 +201,29 @@ func invokeAction(callback unsafe.Pointer, paramsChar *C.char) {
//export startTUN
func startTUN(callback unsafe.Pointer, fd C.int, stackChar, addressChar, dnsChar *C.char) bool {
handleStartTun(callback, int(fd), takeCString(stackChar), takeCString(addressChar), takeCString(dnsChar))
+ if !isRunning {
+ handleStartListener()
+ } else {
+ handleResetConnections()
+ }
return true
}
+//export quickSetup
+func quickSetup(callback unsafe.Pointer, initParamsChar *C.char, setupParamsChar *C.char) {
+ go func() {
+ 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 {
@@ -241,6 +261,9 @@ func sendMessage(message Message) {
//export stopTun
func stopTun() {
handleStopTun()
+ if isRunning {
+ handleStopListener()
+ }
}
//export suspend
diff --git a/lib/application.dart b/lib/application.dart
index e1a1bbc..dbf2dda 100644
--- a/lib/application.dart
+++ b/lib/application.dart
@@ -1,4 +1,5 @@
import 'dart:async';
+import 'dart:io';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:fl_clash/common/common.dart';
@@ -25,6 +26,7 @@ class Application extends ConsumerStatefulWidget {
class ApplicationState extends ConsumerState {
Timer? _autoUpdateProfilesTaskTimer;
+ bool _preHasVpn = false;
final _pageTransitionsTheme = const PageTransitionsTheme(
builders: {
@@ -45,22 +47,22 @@ class ApplicationState extends ConsumerState {
@override
void initState() {
super.initState();
- _autoUpdateProfilesTask();
- globalState.appController = AppController(context, ref);
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
final currentContext = globalState.navigatorKey.currentContext;
if (currentContext != null) {
- globalState.appController = AppController(currentContext, ref);
+ await appController.attach(currentContext, ref);
+ } else {
+ exit(0);
}
- await globalState.appController.init();
- globalState.appController.initLink();
+ _autoUpdateProfilesTask();
+ appController.initLink();
app?.initShortcuts();
});
}
void _autoUpdateProfilesTask() {
_autoUpdateProfilesTaskTimer = Timer(const Duration(minutes: 20), () async {
- await globalState.appController.autoUpdateProfiles();
+ await appController.autoUpdateProfiles();
_autoUpdateProfilesTask();
});
}
@@ -81,11 +83,13 @@ class ApplicationState extends ConsumerState {
child: CoreManager(
child: ConnectivityManager(
onConnectivityChanged: (results) async {
- if (!results.contains(ConnectivityResult.vpn)) {
- coreController.closeConnections();
+ commonPrint.log('connectivityChanged ${results.toString()}');
+ appController.updateLocalIp();
+ final hasVpn = results.contains(ConnectivityResult.vpn);
+ if (_preHasVpn == hasVpn) {
+ appController.addCheckIp();
}
- globalState.appController.updateLocalIp();
- globalState.appController.addCheckIpNumDebounce();
+ _preHasVpn = hasVpn;
},
child: child,
),
@@ -163,8 +167,7 @@ class ApplicationState extends ConsumerState {
linkManager.destroy();
_autoUpdateProfilesTaskTimer?.cancel();
await coreController.destroy();
- await globalState.appController.savePreferences();
- await globalState.appController.handleExit();
+ await appController.handleExit();
super.dispose();
}
}
diff --git a/lib/common/archive.dart b/lib/common/archive.dart
index 37889f2..1d67cb6 100644
--- a/lib/common/archive.dart
+++ b/lib/common/archive.dart
@@ -1,4 +1,3 @@
-import 'dart:convert';
import 'dart:io';
import 'package:archive/archive_io.dart';
@@ -18,14 +17,11 @@ extension ArchiveExt on Archive {
final archiveFile = ArchiveFile(relativePath, data.length, data);
addFile(archiveFile);
}
- // else if (entity is Directory) {
- // addDirectoryToArchive(entity.path, parentPath);
- // }
}
}
- void addTextFile(String name, T raw) {
- final data = json.encode(raw);
- addFile(ArchiveFile.string(name, data));
- }
+ // void addTextFile(String name, T raw) {
+ // final data = json.encode(raw);
+ // addFile(ArchiveFile.string(name, data));
+ // }
}
diff --git a/lib/common/common.dart b/lib/common/common.dart
index b1e05b2..e75514e 100644
--- a/lib/common/common.dart
+++ b/lib/common/common.dart
@@ -6,17 +6,21 @@ export 'constant.dart';
export 'context.dart';
export 'converter.dart';
export 'datetime.dart';
+export 'file.dart';
export 'fixed.dart';
export 'function.dart';
export 'future.dart';
+export 'hive.dart';
export 'http.dart';
export 'icons.dart';
+export 'indexing.dart';
export 'iterable.dart';
export 'keyboard.dart';
export 'launch.dart';
export 'link.dart';
export 'lock.dart';
export 'measure.dart';
+export 'migration.dart';
export 'mixin.dart';
export 'navigation.dart';
export 'navigator.dart';
@@ -32,8 +36,10 @@ export 'proxy.dart';
export 'render.dart';
export 'request.dart';
export 'scroll.dart';
+export 'snowflake.dart';
export 'string.dart';
export 'system.dart';
+export 'task.dart';
export 'text.dart';
export 'tray.dart';
export 'utils.dart';
diff --git a/lib/common/compute.dart b/lib/common/compute.dart
index fa91462..1564a72 100644
--- a/lib/common/compute.dart
+++ b/lib/common/compute.dart
@@ -1,8 +1,7 @@
+import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart';
-import 'string.dart';
-
List computeSort({
required List groups,
required ProxiesSortType sortType,
@@ -10,53 +9,54 @@ List computeSort({
required Map selectedMap,
required String defaultTestUrl,
}) {
+ List sortOfDelay({
+ required List groups,
+ required List proxies,
+ required DelayMap delayMap,
+ required Map selectedMap,
+ required String testUrl,
+ }) {
+ return List.from(proxies)..sort((a, b) {
+ final aDelayState = computeProxyDelayState(
+ proxyName: a.name,
+ testUrl: testUrl,
+ groups: groups,
+ selectedMap: selectedMap,
+ delayMap: delayMap,
+ );
+ final bDelayState = computeProxyDelayState(
+ proxyName: b.name,
+ testUrl: testUrl,
+ groups: groups,
+ selectedMap: selectedMap,
+ delayMap: delayMap,
+ );
+ return aDelayState.compareTo(bDelayState);
+ });
+ }
+
+ List sortOfName(List proxies) {
+ return List.of(proxies)..sort((a, b) => a.name.compareTo(b.name));
+ }
+
return groups.map((group) {
final proxies = group.all;
final newProxies = switch (sortType) {
ProxiesSortType.none => proxies,
- ProxiesSortType.delay => _sortOfDelay(
+ ProxiesSortType.delay => sortOfDelay(
groups: groups,
proxies: proxies,
delayMap: delayMap,
selectedMap: selectedMap,
- testUrl: group.testUrl.getSafeValue(defaultTestUrl),
+ testUrl: group.testUrl.takeFirstValid([defaultTestUrl]),
),
- ProxiesSortType.name => _sortOfName(proxies),
+ ProxiesSortType.name => sortOfName(proxies),
};
return group.copyWith(all: newProxies);
}).toList();
}
-DelayState computeProxyDelayState({
- required String proxyName,
- required String testUrl,
- required List groups,
- required Map selectedMap,
- required DelayMap delayMap,
-}) {
- final state = computeRealSelectedProxyState(
- proxyName,
- groups: groups,
- selectedMap: selectedMap,
- );
- final currentDelayMap = delayMap[state.testUrl.getSafeValue(testUrl)] ?? {};
- final delay = currentDelayMap[state.proxyName];
- return DelayState(delay: delay ?? 0, group: state.group);
-}
-
-SelectedProxyState computeRealSelectedProxyState(
- String proxyName, {
- required List groups,
- required Map selectedMap,
-}) {
- return _getRealSelectedProxyState(
- SelectedProxyState(proxyName: proxyName),
- groups: groups,
- selectedMap: selectedMap,
- );
-}
-
-SelectedProxyState _getRealSelectedProxyState(
+SelectedProxyState getRealSelectedProxyState(
SelectedProxyState state, {
required List groups,
required Map selectedMap,
@@ -72,39 +72,39 @@ SelectedProxyState _getRealSelectedProxyState(
if (currentSelectedName.isEmpty) {
return newState;
}
- return _getRealSelectedProxyState(
+ return getRealSelectedProxyState(
newState.copyWith(proxyName: currentSelectedName, testUrl: group.testUrl),
groups: groups,
selectedMap: selectedMap,
);
}
-List _sortOfDelay({
+SelectedProxyState computeRealSelectedProxyState(
+ String proxyName, {
required List groups,
- required List proxies,
- required DelayMap delayMap,
required Map selectedMap,
- required String testUrl,
}) {
- return List.from(proxies)..sort((a, b) {
- final aDelayState = computeProxyDelayState(
- proxyName: a.name,
- testUrl: testUrl,
- groups: groups,
- selectedMap: selectedMap,
- delayMap: delayMap,
- );
- final bDelayState = computeProxyDelayState(
- proxyName: b.name,
- testUrl: testUrl,
- groups: groups,
- selectedMap: selectedMap,
- delayMap: delayMap,
- );
- return aDelayState.compareTo(bDelayState);
- });
+ return getRealSelectedProxyState(
+ SelectedProxyState(proxyName: proxyName),
+ groups: groups,
+ selectedMap: selectedMap,
+ );
}
-List _sortOfName(List proxies) {
- return List.of(proxies)..sort((a, b) => a.name.compareTo(b.name));
+DelayState computeProxyDelayState({
+ required String proxyName,
+ required String testUrl,
+ required List groups,
+ required Map selectedMap,
+ required DelayMap delayMap,
+}) {
+ final state = computeRealSelectedProxyState(
+ proxyName,
+ groups: groups,
+ selectedMap: selectedMap,
+ );
+ final currentDelayMap =
+ delayMap[state.testUrl.takeFirstValid([testUrl])] ?? {};
+ final delay = currentDelayMap[state.proxyName];
+ return DelayState(delay: delay ?? 0, group: state.group);
}
diff --git a/lib/common/constant.dart b/lib/common/constant.dart
index c924a73..289f73c 100644
--- a/lib/common/constant.dart
+++ b/lib/common/constant.dart
@@ -20,16 +20,18 @@ const helperPort = 47890;
const maxTextScale = 1.4;
const minTextScale = 0.8;
final baseInfoEdgeInsets = EdgeInsets.symmetric(
- vertical: 16.ap,
- horizontal: 16.ap,
+ vertical: 16.mAp,
+ horizontal: 16.mAp,
);
final listHeaderPadding = EdgeInsets.only(
- left: 16.ap,
- right: 8.ap,
- top: 24.ap,
- bottom: 8.ap,
+ left: 16.mAp,
+ right: 8.mAp,
+ top: 24.mAp,
+ bottom: 8.mAp,
);
+const watchExecution = true;
+
final defaultTextScaleFactor =
WidgetsBinding.instance.platformDispatcher.textScaleFactor;
const httpTimeoutDuration = Duration(milliseconds: 5000);
@@ -63,6 +65,7 @@ final commonFilter = ImageFilter.blur(
tileMode: TileMode.mirror,
);
+const listEquality = ListEquality();
const navigationItemListEquality = ListEquality();
const trackerInfoListEquality = ListEquality();
const stringListEquality = ListEquality();
@@ -70,15 +73,18 @@ const intListEquality = ListEquality();
const logListEquality = ListEquality();
const groupListEquality = ListEquality();
const ruleListEquality = ListEquality();
-const scriptEquality = ListEquality