Compare commits

..

6 Commits

Author SHA1 Message Date
chen08209
afbc5adb05 Support override script
Support proxies search

Support svg display

Optimize config persistence

Add some scenes auto close connections

Update core

Optimize more details
2025-06-07 23:52:27 +08:00
chen08209
76c9f08d4a Fix issues that TUN repeat failed to open. 2025-05-01 22:12:05 +08:00
chen08209
f83a8e0cce Update changelog 2025-05-01 13:03:36 +00:00
chen08209
f5544f1af7 Fix windows service verify issues 2025-05-01 20:45:23 +08:00
chen08209
eeb543780a Update changelog 2025-04-30 16:20:40 +00:00
chen08209
676f2d058a Add windows server mode start process verify
Add linux deb dependencies

Add backup recovery strategy select

Support custom text scaling

Optimize the display of different text scale

Optimize windows setup experience

Optimize startTun performance

Optimize android tv experience

Optimize default option

Optimize computed text size

Optimize hyperOS freeform window

Add developer mode

Update core

Optimize more details
2025-05-01 00:02:29 +08:00
192 changed files with 10200 additions and 5905 deletions

View File

@@ -19,7 +19,7 @@ jobs:
os: windows-latest
arch: amd64
- platform: linux
os: ubuntu-latest
os: ubuntu-22.04
arch: amd64
- platform: macos
os: macos-13
@@ -63,11 +63,19 @@ jobs:
cache-dependency-path: |
core/go.sum
- name: Setup Flutter
- 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: ${{ (startsWith(matrix.os, 'windows-11-arm') || startsWith(matrix.os, 'ubuntu-24.04-arm')) && 'master' || 'stable' }}
channel: 'master'
cache: true
- name: Setup Flutter
if: ${{ !(startsWith(matrix.os, 'windows-11-arm') || startsWith(matrix.os, 'ubuntu-24.04-arm')) }}
uses: subosito/flutter-action@v2
with:
channel: 'stable'
cache: true
flutter-version: 3.29.3
- name: Get Flutter Dependency
run: flutter pub get

View File

@@ -1,3 +1,43 @@
## v0.8.84
- Fix windows service verify issues
- Update changelog
## v0.8.83
- Add windows server mode start process verify
- Add linux deb dependencies
- Add backup recovery strategy select
- Support custom text scaling
- Optimize the display of different text scale
- Optimize windows setup experience
- Optimize startTun performance
- Optimize android tv experience
- Optimize default option
- Optimize computed text size
- Optimize hyperOS freeform window
- Add developer mode
- Update core
- Optimize more details
- Add issues template
- Update changelog
## v0.8.82
- Optimize android vpn performance

View File

@@ -41,8 +41,8 @@ on Mobile:
⚠️ Make sure to install the following dependencies before using them
```bash
sudo apt-get install appindicator3-0.1 libappindicator3-dev
sudo apt-get install keybinder-3.0
sudo apt-get install libayatana-appindicator3-dev
sudo apt-get install libkeybinder-3.0-dev
```
### Android

View File

@@ -41,8 +41,8 @@ on Mobile:
⚠️ 使用前请确保安装以下依赖
```bash
sudo apt-get install appindicator3-0.1 libappindicator3-dev
sudo apt-get install keybinder-3.0
sudo apt-get install libayatana-appindicator3-dev
sudo apt-get install libkeybinder-3.0-dev
```
### Android

View File

@@ -1,97 +0,0 @@
plugins {
id "com.android.application"
id "kotlin-android"
id "dev.flutter.flutter-gradle-plugin"
}
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
def defStoreFile = file("keystore.jks")
def defStorePassword = localProperties.getProperty('storePassword')
def defKeyAlias = localProperties.getProperty('keyAlias')
def defKeyPassword = localProperties.getProperty('keyPassword')
def isRelease = defStoreFile.exists() && defStorePassword != null && defKeyAlias != null && defKeyPassword != null
android {
namespace "com.follow.clash"
compileSdk 35
ndkVersion = "28.0.13004108"
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = '17'
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
signingConfigs {
if (isRelease) {
release {
storeFile defStoreFile
storePassword defStorePassword
keyAlias defKeyAlias
keyPassword defKeyPassword
}
}
}
defaultConfig {
applicationId "com.follow.clash"
minSdkVersion 21
targetSdkVersion 35
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
buildTypes {
debug {
minifyEnabled false
applicationIdSuffix '.debug'
}
release {
if (isRelease) {
signingConfig signingConfigs.release
} else {
signingConfig signingConfigs.debug
}
proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
}
}
}
flutter {
source '../..'
}
dependencies {
implementation project(":core")
implementation 'androidx.core:core-splashscreen:1.0.1'
implementation 'com.google.code.gson:gson:2.10.1'
implementation("com.android.tools.smali:smali-dexlib2:3.0.9") {
exclude group: "com.google.guava", module: "guava"
}
}

View File

@@ -0,0 +1,93 @@
import java.util.Properties
plugins {
id("com.android.application")
id("kotlin-android")
id("dev.flutter.flutter-gradle-plugin")
}
val localPropertiesFile = rootProject.file("local.properties")
val localProperties = Properties().apply {
if (localPropertiesFile.exists()) {
localPropertiesFile.inputStream().use { load(it) }
}
}
val mStoreFile: File = file("keystore.jks")
val mStorePassword: String? = localProperties.getProperty("storePassword")
val mKeyAlias: String? = localProperties.getProperty("keyAlias")
val mKeyPassword: String? = localProperties.getProperty("keyPassword")
val isRelease = mStoreFile.exists()
&& mStorePassword != null
&& mKeyAlias != null
&& mKeyPassword != null
android {
namespace = "com.follow.clash"
compileSdk = 35
ndkVersion = "28.0.13004108"
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_17.toString()
}
defaultConfig {
applicationId = "com.follow.clash"
minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
versionName = flutter.versionName
}
signingConfigs {
if (isRelease) {
create("release") {
storeFile = mStoreFile
storePassword = mStorePassword
keyAlias = mKeyAlias
keyPassword = mKeyPassword
}
}
}
buildTypes {
debug {
isMinifyEnabled = false
applicationIdSuffix = ".debug"
}
release {
isMinifyEnabled = true
isDebuggable = false
signingConfig = if (isRelease) {
signingConfigs.getByName("release")
} else {
signingConfigs.getByName("debug")
}
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
}
flutter {
source = "../.."
}
dependencies {
implementation(project(":core"))
implementation("androidx.core:core-splashscreen:1.0.1")
implementation("com.google.code.gson:gson:2.10.1")
implementation("com.android.tools.smali:smali-dexlib2:3.0.9") {
exclude(group = "com.google.guava", module = "guava")
}
}

View File

@@ -7,6 +7,9 @@
<uses-feature
android:name="android.hardware.camera"
android:required="false" />
<uses-feature
android:name="android.software.leanback"
android:required="false" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
@@ -22,6 +25,7 @@
<application
android:name=".FlClashApplication"
android:banner="@mipmap/ic_banner"
android:hardwareAccelerated="true"
android:icon="@mipmap/ic_launcher"
android:label="FlClash">
@@ -88,7 +92,7 @@
<service
android:name=".services.FlClashTileService"
android:exported="true"
android:icon="@drawable/ic_stat_name"
android:icon="@drawable/ic"
android:label="FlClash"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
tools:targetApi="n">

View File

@@ -7,6 +7,10 @@ import com.follow.clash.plugins.VpnPlugin
import io.flutter.FlutterInjector
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.embedding.engine.dart.DartExecutor
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.util.concurrent.locks.ReentrantLock
import kotlin.concurrent.withLock
@@ -33,6 +37,15 @@ object GlobalState {
return currentEngine?.plugins?.get(AppPlugin::class.java) as AppPlugin?
}
fun syncStatus() {
CoroutineScope(Dispatchers.Default).launch {
val status = getCurrentVPNPlugin()?.getStatus() ?: false
withContext(Dispatchers.Main){
runState.value = if (status) RunState.START else RunState.STOP
}
}
}
suspend fun getText(text: String): String {
return getCurrentAppPlugin()?.getText(text) ?: ""
}

View File

@@ -17,6 +17,7 @@ class MainActivity : FlutterActivity() {
override fun onDestroy() {
GlobalState.flutterEngine = null
GlobalState.runState.value = RunState.STOP
super.onDestroy()
}
}

View File

@@ -51,6 +51,7 @@ data object ServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
}
}
private fun handleDestroy() {
GlobalState.destroyServiceEngine()
}

View File

@@ -200,6 +200,13 @@ data object VpnPlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
timerJob = null
}
suspend fun getStatus(): Boolean? {
return withContext(Dispatchers.Default) {
flutterMethodChannel.awaitResult<Boolean>("status", null)
}
}
private fun handleStartService() {
if (flClashService == null) {
bindService()
@@ -248,9 +255,9 @@ data object VpnPlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
GlobalState.runLock.withLock {
if (GlobalState.runState.value == RunState.STOP) return
GlobalState.runState.value = RunState.STOP
flClashService?.stop()
stopForegroundJob()
Core.stopTun()
flClashService?.stop()
GlobalState.handleTryDestroy()
}
}

View File

@@ -16,7 +16,6 @@ import com.follow.clash.MainActivity
import com.follow.clash.R
import com.follow.clash.extensions.getActionPendingIntent
import com.follow.clash.models.VpnOptions
import io.flutter.Log
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
@@ -54,7 +53,7 @@ fun Service.createFlClashNotificationBuilder(): Deferred<NotificationCompat.Buil
this@createFlClashNotificationBuilder, GlobalState.NOTIFICATION_CHANNEL
)
) {
setSmallIcon(R.drawable.ic_stat_name)
setSmallIcon(R.drawable.ic)
setContentTitle("FlClash")
setContentIntent(pendingIntent)
setCategory(NotificationCompat.CATEGORY_SERVICE)
@@ -76,9 +75,8 @@ fun Service.startForeground(notification: Notification) {
val manager = getSystemService(NotificationManager::class.java)
var channel = manager?.getNotificationChannel(GlobalState.NOTIFICATION_CHANNEL)
if (channel == null) {
Log.d("[FlClash]","createNotificationChannel===>")
channel = NotificationChannel(
GlobalState.NOTIFICATION_CHANNEL, "FlClash", NotificationManager.IMPORTANCE_LOW
GlobalState.NOTIFICATION_CHANNEL, "SERVICE_CHANNEL", NotificationManager.IMPORTANCE_LOW
)
manager?.createNotificationChannel(channel)
}

View File

@@ -33,6 +33,7 @@ class FlClashTileService : TileService() {
override fun onStartListening() {
super.onStartListening()
GlobalState.syncStatus()
GlobalState.runState.value?.let { updateTile(it) }
GlobalState.runState.observeForever(observer)
}

View File

@@ -1,7 +1,6 @@
package com.follow.clash.services
import android.annotation.SuppressLint
import android.app.PendingIntent
import android.content.Intent
import android.net.ProxyInfo
import android.net.VpnService

Binary file not shown.

Before

Width:  |  Height:  |  Size: 618 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 423 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 803 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

View 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="#FFFFFF"/>
<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="#FFFFFF"/>
<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="#FFFFFF"/>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -1,33 +0,0 @@
buildscript {
ext.kotlin_version = "${kotlin_version}"
repositories {
google()
mavenCentral()
}
dependencies {
classpath "com.android.tools.build:gradle:$agp_version"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
repositories {
google()
mavenCentral()
}
}
rootProject.buildDir = '../build'
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(':app')
project.evaluationDependsOn(':core')
}
tasks.register("clean", Delete) {
delete rootProject.buildDir
}

22
android/build.gradle.kts Normal file
View File

@@ -0,0 +1,22 @@
allprojects {
repositories {
google()
mavenCentral()
}
}
val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get()
rootProject.layout.buildDirectory.value(newBuildDir)
subprojects {
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
project.layout.buildDirectory.value(newSubprojectBuildDir)
}
subprojects {
project.evaluationDependsOn(":app")
}
tasks.register<Delete>("clean") {
delete(rootProject.layout.buildDirectory)
}

View File

@@ -1,4 +1,4 @@
import com.android.build.gradle.tasks.MergeSourceSetFolders
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.argumentsWithVarargAsSingleArray
plugins {
id("com.android.library")
@@ -12,11 +12,11 @@ android {
defaultConfig {
minSdk = 21
consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
isJniDebuggable = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
@@ -37,13 +37,17 @@ android {
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = "11"
jvmTarget = "17"
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
}
dependencies {
implementation("androidx.annotation:annotation-jvm:1.9.1")
}
val copyNativeLibs by tasks.register<Copy>("copyNativeLibs") {
@@ -58,8 +62,4 @@ afterEvaluate {
tasks.named("preBuild") {
dependsOn(copyNativeLibs)
}
}
dependencies {
implementation("androidx.core:core-ktx:1.16.0")
}

View File

@@ -4,6 +4,8 @@ project("core")
message("CMAKE_SOURCE_DIR ${CMAKE_SOURCE_DIR}")
message("CMAKE_BUILD_TYPE ${CMAKE_BUILD_TYPE}")
if (NOT "${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
add_compile_options(-O3)
@@ -21,6 +23,8 @@ if (NOT "${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
-Wl,--strip-all
-Wl,--exclude-libs=ALL
)
add_compile_options(-fvisibility=hidden -fvisibility-inlines-hidden)
endif ()
set(LIB_CLASH_PATH "${CMAKE_SOURCE_DIR}/../jniLibs/${ANDROID_ABI}/libclash.so")

View File

@@ -1,21 +1,18 @@
#include <jni.h>
#ifdef LIBCLASH
#include <jni.h>
#include <string>
#include "jni_helper.h"
#include "libclash.h"
extern "C"
JNIEXPORT void JNICALL
Java_com_follow_clash_core_Core_startTun(JNIEnv *env, jobject thiz, jint fd, jobject cb) {
auto interface = new_global(cb);
Java_com_follow_clash_core_Core_startTun(JNIEnv *env, jobject, const jint fd, jobject cb) {
const auto interface = new_global(cb);
startTUN(fd, interface);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_follow_clash_core_Core_stopTun(JNIEnv *env, jobject thiz) {
Java_com_follow_clash_core_Core_stopTun(JNIEnv *) {
stopTun();
}
@@ -26,50 +23,50 @@ static jmethodID m_tun_interface_resolve_process;
static void release_jni_object_impl(void *obj) {
ATTACH_JNI();
del_global((jobject) obj);
del_global(static_cast<jobject>(obj));
}
static void call_tun_interface_protect_impl(void *tun_interface, int fd) {
static void call_tun_interface_protect_impl(void *tun_interface, const int fd) {
ATTACH_JNI();
env->CallVoidMethod((jobject) tun_interface,
(jmethodID) m_tun_interface_protect,
(jint) fd);
env->CallVoidMethod(static_cast<jobject>(tun_interface),
m_tun_interface_protect,
fd);
}
static const char*
static const char *
call_tun_interface_resolve_process_impl(void *tun_interface, int protocol,
const char *source,
const char *target,
int uid) {
const char *source,
const char *target,
const int uid) {
ATTACH_JNI();
jstring packageName = (jstring)env->CallObjectMethod((jobject) tun_interface,
(jmethodID) m_tun_interface_resolve_process,
(jint) protocol,
(jstring) new_string(source),
(jstring) new_string(target),
(jint) uid);
const auto packageName = reinterpret_cast<jstring>(env->CallObjectMethod(static_cast<jobject>(tun_interface),
m_tun_interface_resolve_process,
protocol,
new_string(source),
new_string(target),
uid));
return get_string(packageName);
}
extern "C"
JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *vm, void *reserved) {
JNI_OnLoad(JavaVM *vm, void *) {
JNIEnv *env = nullptr;
if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
if (vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK) {
return JNI_ERR;
}
initialize_jni(vm, env);
jclass c_tun_interface = find_class("com/follow/clash/core/TunInterface");
const auto c_tun_interface = find_class("com/follow/clash/core/TunInterface");
m_tun_interface_protect = find_method(c_tun_interface, "protect", "(I)V");
m_tun_interface_resolve_process = find_method(c_tun_interface, "resolverProcess",
"(ILjava/lang/String;Ljava/lang/String;I)Ljava/lang/String;");
"(ILjava/lang/String;Ljava/lang/String;I)Ljava/lang/String;");
registerCallbacks(&call_tun_interface_protect_impl,
&call_tun_interface_resolve_process_impl,
&release_jni_object_impl);
return JNI_VERSION_1_6;
}
#endif
#endif

View File

@@ -1,5 +1,6 @@
#include "jni_helper.h"
#include <cstdlib>
#include <malloc.h>
#include <cstring>
@@ -12,7 +13,7 @@ static jmethodID m_get_bytes;
void initialize_jni(JavaVM *vm, JNIEnv *env) {
global_vm = vm;
c_string = (jclass) new_global(find_class("java/lang/String"));
c_string = reinterpret_cast<jclass>(new_global(find_class("java/lang/String")));
m_new_string = find_method(c_string, "<init>", "([B)V");
m_get_bytes = find_method(c_string, "getBytes", "()[B");
}
@@ -22,23 +23,23 @@ JavaVM *global_java_vm() {
}
char *jni_get_string(JNIEnv *env, jstring str) {
auto array = (jbyteArray) env->CallObjectMethod(str, m_get_bytes);
int length = env->GetArrayLength(array);
char *content = (char *) malloc(length + 1);
env->GetByteArrayRegion(array, 0, length, (jbyte *) content);
const auto array = reinterpret_cast<jbyteArray>(env->CallObjectMethod(str, m_get_bytes));
const int length = env->GetArrayLength(array);
const auto content = static_cast<char *>(malloc(length + 1));
env->GetByteArrayRegion(array, 0, length, reinterpret_cast<jbyte *>(content));
content[length] = 0;
return content;
}
jstring jni_new_string(JNIEnv *env, const char *str) {
auto length = (int) strlen(str);
jbyteArray array = env->NewByteArray(length);
env->SetByteArrayRegion(array, 0, length, (const jbyte *) str);
return (jstring) env->NewObject(c_string, m_new_string, array);
const auto length = static_cast<int>(strlen(str));
const auto array = env->NewByteArray(length);
env->SetByteArrayRegion(array, 0, length, reinterpret_cast<const jbyte *>(str));
return reinterpret_cast<jstring>(env->NewObject(c_string, m_new_string, array));
}
int jni_catch_exception(JNIEnv *env) {
int result = env->ExceptionCheck();
const int result = env->ExceptionCheck();
if (result) {
env->ExceptionDescribe();
env->ExceptionClear();
@@ -46,9 +47,9 @@ int jni_catch_exception(JNIEnv *env) {
return result;
}
void jni_attach_thread(struct scoped_jni *jni) {
void jni_attach_thread(scoped_jni *jni) {
JavaVM *vm = global_java_vm();
if (vm->GetEnv((void **) &jni->env, JNI_VERSION_1_6) == JNI_OK) {
if (vm->GetEnv(reinterpret_cast<void **>(&jni->env), JNI_VERSION_1_6) == JNI_OK) {
jni->require_release = 0;
return;
}
@@ -58,9 +59,9 @@ void jni_attach_thread(struct scoped_jni *jni) {
jni->require_release = 1;
}
void jni_detach_thread(struct scoped_jni *jni) {
void jni_detach_thread(const scoped_jni *env) {
JavaVM *vm = global_java_vm();
if (jni->require_release) {
if (env->require_release) {
vm->DetachCurrentThread();
}
}

View File

@@ -1,9 +1,6 @@
#pragma once
#include <jni.h>
#include <cstdint>
#include <cstdlib>
#include <malloc.h>
struct scoped_jni {
JNIEnv *env;
@@ -18,14 +15,14 @@ extern char *jni_get_string(JNIEnv *env, jstring str);
extern int jni_catch_exception(JNIEnv *env);
extern void jni_attach_thread(struct scoped_jni *jni);
extern void jni_attach_thread(scoped_jni *jni);
extern void jni_detach_thread(struct scoped_jni *env);
extern void jni_detach_thread(const scoped_jni *env);
extern void release_string(char **str);
#define ATTACH_JNI() __attribute__((unused, cleanup(jni_detach_thread))) \
struct scoped_jni _jni; \
scoped_jni _jni{}; \
jni_attach_thread(&_jni); \
JNIEnv *env = _jni.env
@@ -36,4 +33,4 @@ extern void release_string(char **str);
#define new_global(obj) env->NewGlobalRef(obj)
#define del_global(obj) env->DeleteGlobalRef(obj)
#define get_string(jstr) jni_get_string(env, jstr)
#define new_string(cstr) jni_new_string(env, cstr)
#define new_string(cstr) jni_new_string(env, cstr)

View File

@@ -2,4 +2,4 @@ org.gradle.jvmargs=-Xmx4G
android.useAndroidX=true
android.enableJetifier=true
kotlin_version=1.9.22
agp_version=8.9.1
agp_version=8.9.2

View File

@@ -1,27 +0,0 @@
pluginManagement {
def flutterSdkPath = {
def properties = new Properties()
file("local.properties").withInputStream { properties.load(it) }
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
return flutterSdkPath
}
settings.ext.flutterSdkPath = flutterSdkPath()
includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle")
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version "$agp_version" apply false
id "org.jetbrains.kotlin.android" version "$kotlin_version" apply false
}
include ":app"
include ':core'

View File

@@ -0,0 +1,29 @@
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
}
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
plugins {
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
id("com.android.application") version "8.9.2" apply false
id("org.jetbrains.kotlin.android") version "1.9.22" apply false
}
include(":app")
include(":core")

View File

@@ -13,7 +13,6 @@
"resourcesDesc": "External resource related info",
"trafficUsage": "Traffic usage",
"coreInfo": "Core info",
"nullCoreInfoDesc": "Unable to obtain core info",
"networkSpeed": "Network speed",
"outboundMode": "Outbound mode",
"networkDetection": "Network detection",
@@ -22,7 +21,6 @@
"noProxy": "No proxy",
"noProxyDesc": "Please create a profile or add a valid profile",
"nullProfileDesc": "No profile, Please add a profile",
"nullLogsDesc": "No logs",
"settings": "Settings",
"language": "Language",
"defaultText": "Default",
@@ -149,8 +147,6 @@
"addressHelp": "WebDAV server address",
"addressTip": "Please enter a valid WebDAV address",
"password": "Password",
"passwordTip": "Password cannot be empty",
"accountTip": "Account cannot be empty",
"checkUpdate": "Check for updates",
"discoverNewVersion": "Discover the new version",
"checkUpdateError": "The current application is already the latest version",
@@ -185,8 +181,6 @@
"expirationTime": "Expiration time",
"connections": "Connections",
"connectionsDesc": "View current connections data",
"nullRequestsDesc": "No requests",
"nullConnectionsDesc": "No connections",
"intranetIP": "Intranet IP",
"view": "View",
"cut": "Cut",
@@ -219,7 +213,6 @@
"autoCloseConnectionsDesc": "Auto close connections after change node",
"onlyStatisticsProxy": "Only statistics proxy",
"onlyStatisticsProxyDesc": "When turned on, only statistics proxy traffic",
"deleteProfileTip": "Sure you want to delete the current profile?",
"pureBlackMode": "Pure black mode",
"keepAliveIntervalDesc": "Tcp keep alive interval",
"entries": " entries",
@@ -250,7 +243,6 @@
"dnsDesc": "Update DNS related settings",
"key": "Key",
"value": "Value",
"notEmpty": "Cannot be empty",
"hostsDesc": "Add Hosts",
"vpnTip": "Changes take effect after restarting the VPN",
"vpnEnableDesc": "Auto routes all system traffic through VpnService",
@@ -337,15 +329,12 @@
"fileIsUpdate": "The file has been modified. Do you want to save the changes?",
"profileHasUpdate": "The profile has been modified. Do you want to disable auto update?",
"hasCacheChange": "Do you want to cache the changes?",
"nullProxies": "No proxies",
"copySuccess": "Copy success",
"copyLink": "Copy link",
"exportFile": "Export file",
"cacheCorrupt": "The cache is corrupt. Do you want to clear it?",
"detectionTip": "Relying on third-party api is for reference only",
"listen": "Listen",
"keyExists": "The current key already exists",
"valueExists": "The current value already exists",
"undo": "undo",
"redo": "redo",
"none": "none",
@@ -353,28 +342,21 @@
"basicConfigDesc": "Modify the basic configuration globally",
"selectedCountTitle": "{count} items have been selected",
"addRule": "Add rule",
"ruleProviderEmptyTip": "Rule provider cannot be empty",
"ruleName": "Rule name",
"content": "Content",
"contentEmptyTip": "Content cannot be empty",
"subRule": "Sub rule",
"subRuleEmptyTip": "Sub rule content cannot be empty",
"ruleTarget": "Rule target",
"ruleTargetEmptyTip": "Rule target cannot be empty",
"sourceIp": "Source IP",
"noResolve": "No resolve IP",
"getOriginRules": "Get original rules",
"overrideOriginRules": "Override the original rule",
"addedOriginRules": "Attach on the original rules",
"enableOverride": "Enable override",
"deleteRuleTip": "Are you sure you want to delete the selected rule?",
"saveChanges": "Do you want to save the changes?",
"generalDesc": "Modify general settings",
"findProcessModeDesc": "There is a certain performance loss after opening",
"tabAnimationDesc": "Effective only in mobile view",
"saveTip": "Are you sure you want to save?",
"deleteColorTip": "Are you sure you want to delete the current color?",
"colorExists": "Current color already exists",
"colorSchemes": "Color schemes",
"palette": "Palette",
"tonalSpotScheme": "TonalSpot",
@@ -395,5 +377,34 @@
"textScale": "Text Scaling",
"internet": "Internet",
"systemApp": "System APP",
"noNetworkApp": "No network App"
"noNetworkApp": "No network APP",
"contactMe": "Contact me",
"recoveryStrategy": "Recovery strategy",
"recoveryStrategy_override": "Override",
"recoveryStrategy_compatible": "Compatible",
"logsTest": "Logs test",
"emptyTip": "{label} cannot be empty",
"urlTip": "{label} must be a url",
"numberTip": "{label} must be a number",
"interval": "Interval",
"existsTip": "Current {label} already exists",
"deleteTip": "Are you sure you want to delete the current {label}?",
"deleteMultipTip": "Are you sure you want to delete the selected {label}?",
"nullTip": "No {label} at the moment",
"script": "Script",
"color": "Color",
"rename": "Rename",
"unnamed": "Unnamed",
"pleaseEnterScriptName": "Please enter a script name",
"overrideInvalidTip": "Does not take effect in script mode",
"mixedPort": "Mixed Port",
"socksPort": "Socks Port",
"redirPort": "Redir Port",
"tproxyPort": "Tproxy Port",
"portTip": "{label} must be between 1024 and 49151",
"portConflictTip": "Please enter a different port",
"import": "Import",
"importFile": "Import from file",
"importUrl": "Import from URL",
"autoSetSystemDns": "Auto set system DNS"
}

View File

@@ -13,7 +13,6 @@
"resourcesDesc": "外部リソース関連情報",
"trafficUsage": "トラフィック使用量",
"coreInfo": "コア情報",
"nullCoreInfoDesc": "コア情報を取得できません",
"networkSpeed": "ネットワーク速度",
"outboundMode": "アウトバウンドモード",
"networkDetection": "ネットワーク検出",
@@ -22,7 +21,6 @@
"noProxy": "プロキシなし",
"noProxyDesc": "プロファイルを作成するか、有効なプロファイルを追加してください",
"nullProfileDesc": "プロファイルがありません。追加してください",
"nullLogsDesc": "ログがありません",
"settings": "設定",
"language": "言語",
"defaultText": "デフォルト",
@@ -149,8 +147,6 @@
"addressHelp": "WebDAVサーバーアドレス",
"addressTip": "有効なWebDAVアドレスを入力",
"password": "パスワード",
"passwordTip": "パスワードは必須です",
"accountTip": "アカウントは必須です",
"checkUpdate": "更新を確認",
"discoverNewVersion": "新バージョンを発見",
"checkUpdateError": "アプリは最新版です",
@@ -185,8 +181,6 @@
"expirationTime": "有効期限",
"connections": "接続",
"connectionsDesc": "現在の接続データを表示",
"nullRequestsDesc": "リクエストなし",
"nullConnectionsDesc": "接続なし",
"intranetIP": "イントラネットIP",
"view": "表示",
"cut": "切り取り",
@@ -219,7 +213,6 @@
"autoCloseConnectionsDesc": "ノード変更後に接続を自動閉じる",
"onlyStatisticsProxy": "プロキシのみ統計",
"onlyStatisticsProxyDesc": "有効化するとプロキシトラフィックのみ統計",
"deleteProfileTip": "現在のプロファイルを削除しますか?",
"pureBlackMode": "純黒モード",
"keepAliveIntervalDesc": "TCPキープアライブ間隔",
"entries": " エントリ",
@@ -250,7 +243,6 @@
"dnsDesc": "DNS関連設定の更新",
"key": "キー",
"value": "値",
"notEmpty": "空欄不可",
"hostsDesc": "ホストを追加",
"vpnTip": "変更はVPN再起動後に有効",
"vpnEnableDesc": "VpnService経由で全システムトラフィックをルーティング",
@@ -337,15 +329,12 @@
"fileIsUpdate": "ファイルが変更されました。保存しますか?",
"profileHasUpdate": "プロファイルが変更されました。自動更新を無効化しますか?",
"hasCacheChange": "変更をキャッシュしますか?",
"nullProxies": "プロキシなし",
"copySuccess": "コピー成功",
"copyLink": "リンクをコピー",
"exportFile": "ファイルをエクスポート",
"cacheCorrupt": "キャッシュが破損しています。クリアしますか?",
"detectionTip": "サードパーティAPIに依存参考値",
"listen": "リスン",
"keyExists": "現在のキーは既に存在します",
"valueExists": "現在の値は既に存在します",
"undo": "元に戻す",
"redo": "やり直す",
"none": "なし",
@@ -353,28 +342,21 @@
"basicConfigDesc": "基本設定をグローバルに変更",
"selectedCountTitle": "{count} 項目が選択されています",
"addRule": "ルールを追加",
"ruleProviderEmptyTip": "ルールプロバイダーは必須です",
"ruleName": "ルール名",
"content": "内容",
"contentEmptyTip": "内容は必須です",
"subRule": "サブルール",
"subRuleEmptyTip": "サブルールの内容は必須です",
"ruleTarget": "ルール対象",
"ruleTargetEmptyTip": "ルール対象は必須です",
"sourceIp": "送信元IP",
"noResolve": "IPを解決しない",
"getOriginRules": "元のルールを取得",
"overrideOriginRules": "元のルールを上書き",
"addedOriginRules": "元のルールに追加",
"enableOverride": "上書きを有効化",
"deleteRuleTip": "選択したルールを削除しますか?",
"saveChanges": "変更を保存しますか?",
"generalDesc": "一般設定を変更",
"findProcessModeDesc": "有効化するとパフォーマンスが若干低下します",
"tabAnimationDesc": "モバイル表示でのみ有効",
"saveTip": "保存してもよろしいですか?",
"deleteColorTip": "現在の色を削除しますか?",
"colorExists": "この色は既に存在します",
"colorSchemes": "カラースキーム",
"palette": "パレット",
"tonalSpotScheme": "トーンスポット",
@@ -396,5 +378,34 @@
"textScale": "テキストスケーリング",
"internet": "インターネット",
"systemApp": "システムアプリ",
"noNetworkApp": "ネットワークなしアプリ"
"noNetworkApp": "ネットワークなしアプリ",
"contactMe": "連絡する",
"recoveryStrategy": "リカバリー戦略",
"recoveryStrategy_override": "オーバーライド",
"recoveryStrategy_compatible": "互換性",
"logsTest": "ログテスト",
"emptyTip": "{label}は空欄にできません",
"urlTip": "{label}はURLである必要があります",
"numberTip": "{label}は数字でなければなりません",
"interval": "インターバル",
"existsTip": "現在の{label}は既に存在しています",
"deleteTip": "現在の{label}を削除してもよろしいですか?",
"deleteMultipTip": "選択された{label}を削除してもよろしいですか?",
"nullTip": "現在{label}はありません",
"script": "スクリプト",
"color": "カラー",
"rename": "リネーム",
"unnamed": "無題",
"pleaseEnterScriptName": "スクリプト名を入力してください",
"overrideInvalidTip": "スクリプトモードでは有効になりません",
"mixedPort": "混合ポート",
"socksPort": "Socksポート",
"redirPort": "Redirポート",
"tproxyPort": "Tproxyポート",
"portTip": "{label} は 1024 から 49151 の間でなければなりません",
"portConflictTip": "別のポートを入力してください",
"import": "インポート",
"importFile": "ファイルからインポート",
"importUrl": "URLからインポート",
"autoSetSystemDns": "オートセットシステムDNS"
}

View File

@@ -13,7 +13,6 @@
"resourcesDesc": "Информация, связанная с внешними ресурсами",
"trafficUsage": "Использование трафика",
"coreInfo": "Информация о ядре",
"nullCoreInfoDesc": "Не удалось получить информацию о ядре",
"networkSpeed": "Скорость сети",
"outboundMode": "Режим исходящего трафика",
"networkDetection": "Обнаружение сети",
@@ -22,7 +21,6 @@
"noProxy": "Нет прокси",
"noProxyDesc": "Пожалуйста, создайте профиль или добавьте действительный профиль",
"nullProfileDesc": "Нет профиля, пожалуйста, добавьте профиль",
"nullLogsDesc": "Нет логов",
"settings": "Настройки",
"language": "Язык",
"defaultText": "По умолчанию",
@@ -149,8 +147,6 @@
"addressHelp": "Адрес сервера WebDAV",
"addressTip": "Пожалуйста, введите действительный адрес WebDAV",
"password": "Пароль",
"passwordTip": "Пароль не может быть пустым",
"accountTip": "Аккаунт не может быть пустым",
"checkUpdate": "Проверить обновления",
"discoverNewVersion": "Обнаружена новая версия",
"checkUpdateError": "Текущее приложение уже является последней версией",
@@ -185,8 +181,6 @@
"expirationTime": "Время истечения",
"connections": "Соединения",
"connectionsDesc": "Просмотр текущих данных о соединениях",
"nullRequestsDesc": "Нет запросов",
"nullConnectionsDesc": "Нет соединений",
"intranetIP": "Внутренний IP",
"view": "Просмотр",
"cut": "Вырезать",
@@ -219,7 +213,6 @@
"autoCloseConnectionsDesc": "Автоматически закрывать соединения после смены узла",
"onlyStatisticsProxy": "Только статистика прокси",
"onlyStatisticsProxyDesc": "При включении будет учитываться только трафик прокси",
"deleteProfileTip": "Вы уверены, что хотите удалить текущий профиль?",
"pureBlackMode": "Чисто черный режим",
"keepAliveIntervalDesc": "Интервал поддержания TCP-соединения",
"entries": " записей",
@@ -250,7 +243,6 @@
"dnsDesc": "Обновление настроек, связанных с DNS",
"key": "Ключ",
"value": "Значение",
"notEmpty": "Не может быть пустым",
"hostsDesc": "Добавить Hosts",
"vpnTip": "Изменения вступят в силу после перезапуска VPN",
"vpnEnableDesc": "Автоматически направляет весь системный трафик через VpnService",
@@ -337,15 +329,12 @@
"fileIsUpdate": "Файл был изменен. Хотите сохранить изменения?",
"profileHasUpdate": "Профиль был изменен. Хотите отключить автообновление?",
"hasCacheChange": "Хотите сохранить изменения в кэше?",
"nullProxies": "Нет прокси",
"copySuccess": "Копирование успешно",
"copyLink": "Копировать ссылку",
"exportFile": "Экспорт файла",
"cacheCorrupt": "Кэш поврежден. Хотите очистить его?",
"detectionTip": "Опирается на сторонний API, только для справки",
"listen": "Слушать",
"keyExists": "Текущий ключ уже существует",
"valueExists": "Текущее значение уже существует",
"undo": "Отменить",
"redo": "Повторить",
"none": "Нет",
@@ -353,28 +342,21 @@
"basicConfigDesc": "Глобальное изменение базовых настроек",
"selectedCountTitle": "Выбрано {count} элементов",
"addRule": "Добавить правило",
"ruleProviderEmptyTip": "Поставщик правил не может быть пустым",
"ruleName": "Название правила",
"content": "Содержание",
"contentEmptyTip": "Содержание не может быть пустым",
"subRule": "Подправило",
"subRuleEmptyTip": "Содержание подправила не может быть пустым",
"ruleTarget": "Цель правила",
"ruleTargetEmptyTip": "Цель правила не может быть пустой",
"sourceIp": "Исходный IP",
"noResolve": "Не разрешать IP",
"getOriginRules": "Получить оригинальные правила",
"overrideOriginRules": "Переопределить оригинальное правило",
"addedOriginRules": "Добавить к оригинальным правилам",
"enableOverride": "Включить переопределение",
"deleteRuleTip": "Вы уверены, что хотите удалить выбранное правило?",
"saveChanges": "Сохранить изменения?",
"generalDesc": "Изменение общих настроек",
"findProcessModeDesc": "При включении возможны небольшие потери производительности",
"tabAnimationDesc": "Действительно только в мобильном виде",
"saveTip": "Вы уверены, что хотите сохранить?",
"deleteColorTip": "Удалить текущий цвет?",
"colorExists": "Этот цвет уже существует",
"colorSchemes": "Цветовые схемы",
"palette": "Палитра",
"tonalSpotScheme": "Тональный акцент",
@@ -396,5 +378,34 @@
"textScale": "Масштабирование текста",
"internet": "Интернет",
"systemApp": "Системное приложение",
"noNetworkApp": "Приложение без сети"
"noNetworkApp": "Приложение без сети",
"contactMe": "Свяжитесь со мной",
"recoveryStrategy": "Стратегия восстановления",
"recoveryStrategy_override": "Переопределение",
"recoveryStrategy_compatible": "Совместимый",
"logsTest": "Тест журналов",
"emptyTip": "{label} не может быть пустым",
"urlTip": "{label} должен быть URL",
"numberTip": "{label} должно быть числом",
"interval": "Интервал",
"existsTip": "Текущий {label} уже существует",
"deleteTip": "Вы уверены, что хотите удалить текущий {label}?",
"deleteMultipTip": "Вы уверены, что хотите удалить выбранные {label}?",
"nullTip": "Сейчас {label} нет",
"script": "Скрипт",
"color": "Цвет",
"rename": "Переименовать",
"unnamed": "Без имени",
"pleaseEnterScriptName": "Пожалуйста, введите название скрипта",
"overrideInvalidTip": "В скриптовом режиме не действует",
"mixedPort": "Смешанный порт",
"socksPort": "Socks-порт",
"redirPort": "Redir-порт",
"tproxyPort": "Tproxy-порт",
"portTip": "{label} должен быть числом от 1024 до 49151",
"portConflictTip": "Введите другой порт",
"import": "Импорт",
"importFile": "Импорт из файла",
"importUrl": "Импорт по URL",
"autoSetSystemDns": "Автоматическая настройка системного DNS"
}

View File

@@ -13,7 +13,6 @@
"resourcesDesc": "外部资源相关信息",
"trafficUsage": "流量统计",
"coreInfo": "内核信息",
"nullCoreInfoDesc": "无法获取内核信息",
"networkSpeed": "网络速度",
"outboundMode": "出站模式",
"networkDetection": "网络检测",
@@ -22,7 +21,6 @@
"noProxy": "暂无代理",
"noProxyDesc": "请创建配置文件或者添加有效配置文件",
"nullProfileDesc": "没有配置文件,请先添加配置文件",
"nullLogsDesc": "暂无日志",
"settings": "设置",
"language": "语言",
"defaultText": "默认",
@@ -149,8 +147,6 @@
"addressHelp": "WebDAV服务器地址",
"addressTip": "请输入有效的WebDAV地址",
"password": "密码",
"passwordTip": "密码不能为空",
"accountTip": "账号不能为空",
"checkUpdate": "检查更新",
"discoverNewVersion": "发现新版本",
"checkUpdateError": "当前应用已经是最新版了",
@@ -185,8 +181,6 @@
"expirationTime": "到期时间",
"connections": "连接",
"connectionsDesc": "查看当前连接数据",
"nullRequestsDesc": "暂无请求",
"nullConnectionsDesc": "暂无连接",
"intranetIP": "内网 IP",
"view": "查看",
"cut": "剪切",
@@ -219,7 +213,6 @@
"autoCloseConnectionsDesc": "切换节点后自动关闭连接",
"onlyStatisticsProxy": "仅统计代理",
"onlyStatisticsProxyDesc": "开启后,将只统计代理流量",
"deleteProfileTip": "确定要删除当前配置吗?",
"pureBlackMode": "纯黑模式",
"keepAliveIntervalDesc": "TCP保持活动间隔",
"entries": "个条目",
@@ -250,7 +243,6 @@
"dnsDesc": "更新DNS相关设置",
"key": "键",
"value": "值",
"notEmpty": "不能为空",
"hostsDesc": "追加Hosts",
"vpnTip": "重启VPN后改变生效",
"vpnEnableDesc": "通过VpnService自动路由系统所有流量",
@@ -337,15 +329,12 @@
"fileIsUpdate": "文件有修改,是否保存修改",
"profileHasUpdate": "配置文件已经修改,是否关闭自动更新 ",
"hasCacheChange": "是否缓存修改",
"nullProxies": "暂无代理",
"copySuccess": "复制成功",
"copyLink": "复制链接",
"exportFile": "导出文件",
"cacheCorrupt": "缓存已损坏,是否清空?",
"detectionTip": "依赖第三方api仅供参考",
"listen": "监听",
"keyExists": "当前键已存在",
"valueExists": "当前值已存在",
"undo": "撤销",
"redo": "重做",
"none": "无",
@@ -353,28 +342,21 @@
"basicConfigDesc": "全局修改基本配置",
"selectedCountTitle": "已选择 {count} 项",
"addRule": "添加规则",
"ruleProviderEmptyTip": "规则提供者不能为空",
"ruleName": "规则名称",
"content": "内容",
"contentEmptyTip": "内容不能为空",
"subRule": "子规则",
"subRuleEmptyTip": "子规则内容不能为空",
"ruleTarget": "规则目标",
"ruleTargetEmptyTip": "规则目标不能为空",
"sourceIp": "源IP",
"noResolve": "不解析IP",
"getOriginRules": "获取原始规则",
"overrideOriginRules": "覆盖原始规则",
"addedOriginRules": "附加到原始规则",
"enableOverride": "启用覆写",
"deleteRuleTip": "确定要删除选中的规则吗?",
"saveChanges": "是否保存更改?",
"generalDesc": "修改通用设置",
"findProcessModeDesc": "开启后会有一定性能损耗",
"tabAnimationDesc": "仅在移动视图中有效",
"saveTip": "确定要保存吗?",
"deleteColorTip": "确定删除当前颜色吗?",
"colorExists": "该颜色已存在",
"colorSchemes": "配色方案",
"palette": "调色板",
"tonalSpotScheme": "调性点缀",
@@ -396,5 +378,34 @@
"textScale": "文本缩放",
"internet": "互联网",
"systemApp": "系统应用",
"noNetworkApp": "无网络应用"
"noNetworkApp": "无网络应用",
"contactMe": "联系我",
"recoveryStrategy": "恢复策略",
"recoveryStrategy_override": "覆盖",
"recoveryStrategy_compatible": "兼容",
"logsTest": "日志测试",
"emptyTip": "{label}不能为空",
"urlTip": "{label}必须为URL",
"numberTip": "{label}必须为数字",
"interval": "间隔",
"existsTip": "{label}当前已存在",
"deleteTip": "确定删除当前{label}吗?",
"deleteMultipTip": "确定删除选中的{label}吗?",
"nullTip": "暂无{label}",
"script": "脚本",
"color": "颜色",
"rename": "重命名",
"unnamed": "未命名",
"pleaseEnterScriptName": "请输入脚本名称",
"overrideInvalidTip": "在脚本模式下不生效",
"mixedPort": "混合端口",
"socksPort": "Socks端口",
"redirPort": "Redir端口",
"tproxyPort": "Tproxy端口",
"portTip": "{label} 必须在 1024 到 49151 之间",
"portConflictTip": "请输入不同的端口",
"import": "导入",
"importFile": "通过文件导入",
"importUrl": "通过URL导入",
"autoSetSystemDns": "自动设置系统DNS"
}

View File

@@ -5,16 +5,17 @@ import (
)
type Action struct {
Id string `json:"id"`
Method Method `json:"method"`
Data interface{} `json:"data"`
DefaultValue interface{} `json:"default-value"`
Id string `json:"id"`
Method Method `json:"method"`
Data interface{} `json:"data"`
}
type ActionResult struct {
Id string `json:"id"`
Method Method `json:"method"`
Data interface{} `json:"data"`
Code int `json:"code"`
Port int64
}
func (result ActionResult) Json() ([]byte, error) {
@@ -22,99 +23,117 @@ func (result ActionResult) Json() ([]byte, error) {
return data, err
}
func (action Action) getResult(data interface{}) []byte {
resultAction := ActionResult{
Id: action.Id,
Method: action.Method,
Data: data,
}
res, _ := resultAction.Json()
return res
func (result ActionResult) success(data interface{}) {
result.Code = 0
result.Data = data
result.send()
}
func handleAction(action *Action, result func(data interface{})) {
func (result ActionResult) error(data interface{}) {
result.Code = -1
result.Data = data
result.send()
}
func handleAction(action *Action, result ActionResult) {
switch action.Method {
case initClashMethod:
paramsString := action.Data.(string)
result(handleInitClash(paramsString))
result.success(handleInitClash(paramsString))
return
case getIsInitMethod:
result(handleGetIsInit())
result.success(handleGetIsInit())
return
case forceGcMethod:
handleForceGc()
result(true)
result.success(true)
return
case shutdownMethod:
result(handleShutdown())
result.success(handleShutdown())
return
case validateConfigMethod:
data := []byte(action.Data.(string))
result(handleValidateConfig(data))
result.success(handleValidateConfig(data))
return
case updateConfigMethod:
data := []byte(action.Data.(string))
result(handleUpdateConfig(data))
result.success(handleUpdateConfig(data))
return
case setupConfigMethod:
data := []byte(action.Data.(string))
result.success(handleSetupConfig(data))
return
case getProxiesMethod:
result(handleGetProxies())
result.success(handleGetProxies())
return
case changeProxyMethod:
data := action.Data.(string)
handleChangeProxy(data, func(value string) {
result(value)
result.success(value)
})
return
case getTrafficMethod:
result(handleGetTraffic())
result.success(handleGetTraffic())
return
case getTotalTrafficMethod:
result(handleGetTotalTraffic())
result.success(handleGetTotalTraffic())
return
case resetTrafficMethod:
handleResetTraffic()
result(true)
result.success(true)
return
case asyncTestDelayMethod:
data := action.Data.(string)
handleAsyncTestDelay(data, func(value string) {
result(value)
result.success(value)
})
return
case getConnectionsMethod:
result(handleGetConnections())
result.success(handleGetConnections())
return
case closeConnectionsMethod:
result(handleCloseConnections())
result.success(handleCloseConnections())
return
case resetConnectionsMethod:
result.success(handleResetConnections())
return
case getConfigMethod:
path := action.Data.(string)
config, err := handleGetConfig(path)
if err != nil {
result.error(err)
return
}
result.success(config)
return
case closeConnectionMethod:
id := action.Data.(string)
result(handleCloseConnection(id))
result.success(handleCloseConnection(id))
return
case getExternalProvidersMethod:
result(handleGetExternalProviders())
result.success(handleGetExternalProviders())
return
case getExternalProviderMethod:
externalProviderName := action.Data.(string)
result(handleGetExternalProvider(externalProviderName))
result.success(handleGetExternalProvider(externalProviderName))
case updateGeoDataMethod:
paramsString := action.Data.(string)
var params = map[string]string{}
err := json.Unmarshal([]byte(paramsString), &params)
if err != nil {
result(err.Error())
result.success(err.Error())
return
}
geoType := params["geo-type"]
geoName := params["geo-name"]
handleUpdateGeoData(geoType, geoName, func(value string) {
result(value)
result.success(value)
})
return
case updateExternalProviderMethod:
providerName := action.Data.(string)
handleUpdateExternalProvider(providerName, func(value string) {
result(value)
result.success(value)
})
return
case sideLoadExternalProviderMethod:
@@ -122,59 +141,48 @@ func handleAction(action *Action, result func(data interface{})) {
var params = map[string]string{}
err := json.Unmarshal([]byte(paramsString), &params)
if err != nil {
result(err.Error())
result.success(err.Error())
return
}
providerName := params["providerName"]
data := params["data"]
handleSideLoadExternalProvider(providerName, []byte(data), func(value string) {
result(value)
result.success(value)
})
return
case startLogMethod:
handleStartLog()
result(true)
result.success(true)
return
case stopLogMethod:
handleStopLog()
result(true)
result.success(true)
return
case startListenerMethod:
result(handleStartListener())
result.success(handleStartListener())
return
case stopListenerMethod:
result(handleStopListener())
result.success(handleStopListener())
return
case getCountryCodeMethod:
ip := action.Data.(string)
handleGetCountryCode(ip, func(value string) {
result(value)
result.success(value)
})
return
case getMemoryMethod:
handleGetMemory(func(value string) {
result(value)
})
return
case getProfileMethod:
profileId := action.Data.(string)
handleGetMemory(func(value string) {
result(handleGetProfile(profileId))
result.success(value)
})
return
case setStateMethod:
data := action.Data.(string)
handleSetState(data)
result(true)
result.success(true)
case crashMethod:
result(true)
result.success(true)
handleCrash()
default:
handle := nextHandle(action, result)
if handle {
return
} else {
result(action.DefaultValue)
}
nextHandle(action, result)
}
}

View File

@@ -43,13 +43,13 @@ var (
}
)
func protect(callback unsafe.Pointer, fd int) {
func Protect(callback unsafe.Pointer, fd int) {
if globalCallbacks.protectFunc != nil {
C.protect(globalCallbacks.protectFunc, callback, C.int(fd))
}
}
func resolveProcess(callback unsafe.Pointer, protocol int, source, target string, uid int) string {
func ResolveProcess(callback unsafe.Pointer, protocol int, source, target string, uid int) string {
if globalCallbacks.resolveProcessFunc == nil {
return ""
}

View File

@@ -1,9 +1,10 @@
package main
import (
b "bytes"
"context"
"encoding/json"
"errors"
"fmt"
"github.com/metacubex/mihomo/adapter"
"github.com/metacubex/mihomo/adapter/inbound"
"github.com/metacubex/mihomo/adapter/outboundgroup"
@@ -21,31 +22,16 @@ import (
"github.com/metacubex/mihomo/log"
rp "github.com/metacubex/mihomo/rules/provider"
"github.com/metacubex/mihomo/tunnel"
"github.com/samber/lo"
"os"
"path/filepath"
"strings"
"sync"
)
func splitByMultipleSeparators(s string) interface{} {
isSeparator := func(r rune) bool {
return r == ',' || r == ' ' || r == ';'
}
parts := strings.FieldsFunc(s, isSeparator)
if len(parts) > 1 {
return parts
}
return s
}
var (
version = 0
isRunning = false
runLock sync.Mutex
ips = []string{"ipwho.is", "api.ip.sb", "ipapi.co", "ipinfo.io"}
b, _ = batch.New[bool](context.Background(), batch.WithConcurrencyNum[bool](50))
currentConfig *config.Config
version = 0
isRunning = false
runLock sync.Mutex
mBatch, _ = batch.New[bool](context.Background(), batch.WithConcurrencyNum[bool](50))
)
type ExternalProviders []ExternalProvider
@@ -54,54 +40,6 @@ 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 readFile(path string) ([]byte, error) {
if _, err := os.Stat(path); os.IsNotExist(err) {
return nil, err
}
data, err := os.ReadFile(path)
if err != nil {
return nil, err
}
return data, err
}
func getProfilePath(id string) string {
return filepath.Join(constant.Path.HomeDir(), "profiles", id+".yaml")
}
func getProfileProvidersPath(id string) string {
return filepath.Join(constant.Path.HomeDir(), "providers", id)
}
func getRawConfigWithId(id string) *config.RawConfig {
path := getProfilePath(id)
bytes, err := readFile(path)
if err != nil {
return config.DefaultRawConfig()
}
prof, err := config.UnmarshalRawConfig(bytes)
if err != nil {
log.Errorln("unmarshalRawConfig error %v", err)
return config.DefaultRawConfig()
}
for _, mapping := range prof.ProxyProvider {
value, exist := mapping["path"].(string)
if !exist {
continue
}
mapping["path"] = filepath.Join(getProfileProvidersPath(id), value)
}
for _, mapping := range prof.RuleProvider {
value, exist := mapping["path"].(string)
if !exist {
continue
}
mapping["path"] = filepath.Join(getProfileProvidersPath(id), value)
}
return prof
}
func getExternalProvidersRaw() map[string]cp.Provider {
eps := make(map[string]cp.Provider)
for n, p := range tunnel.Providers() {
@@ -166,143 +104,15 @@ func sideUpdateExternalProvider(p cp.Provider, bytes []byte) error {
}
}
func decorationConfig(profileId string, cfg config.RawConfig) *config.RawConfig {
prof := getRawConfigWithId(profileId)
overwriteConfig(prof, cfg)
return prof
}
func attachHosts(hosts, patchHosts map[string]any) {
for k, v := range patchHosts {
if str, ok := v.(string); ok {
hosts[k] = splitByMultipleSeparators(str)
}
}
}
func updatePatchDns(dns config.RawDNS) {
for pair := dns.NameServerPolicy.Oldest(); pair != nil; pair = pair.Next() {
if str, ok := pair.Value.(string); ok {
dns.NameServerPolicy.Set(pair.Key, splitByMultipleSeparators(str))
}
}
}
func trimArr(arr []string) (r []string) {
for _, e := range arr {
r = append(r, strings.Trim(e, " "))
}
return
}
func overrideRules(rules, patchRules []string) []string {
target := ""
for _, line := range rules {
rule := trimArr(strings.Split(line, ","))
if len(rule) != 2 {
continue
}
if strings.EqualFold(rule[0], "MATCH") {
target = rule[1]
break
}
}
if target == "" {
return rules
}
rulesExt := lo.Map(ips, func(ip string, _ int) string {
return fmt.Sprintf("DOMAIN,%s,%s", ip, target)
})
return append(append(rulesExt, patchRules...), rules...)
}
func overwriteConfig(targetConfig *config.RawConfig, patchConfig config.RawConfig) {
targetConfig.ExternalController = patchConfig.ExternalController
targetConfig.ExternalUI = ""
targetConfig.Interface = ""
targetConfig.ExternalUIURL = ""
targetConfig.TCPConcurrent = patchConfig.TCPConcurrent
targetConfig.UnifiedDelay = patchConfig.UnifiedDelay
targetConfig.IPv6 = patchConfig.IPv6
targetConfig.LogLevel = patchConfig.LogLevel
targetConfig.Port = 0
targetConfig.SocksPort = 0
targetConfig.KeepAliveInterval = patchConfig.KeepAliveInterval
targetConfig.MixedPort = patchConfig.MixedPort
targetConfig.FindProcessMode = patchConfig.FindProcessMode
targetConfig.AllowLan = patchConfig.AllowLan
targetConfig.Mode = patchConfig.Mode
targetConfig.Tun.Enable = patchConfig.Tun.Enable
targetConfig.Tun.Device = patchConfig.Tun.Device
targetConfig.Tun.DNSHijack = patchConfig.Tun.DNSHijack
targetConfig.Tun.Stack = patchConfig.Tun.Stack
targetConfig.Tun.RouteAddress = patchConfig.Tun.RouteAddress
targetConfig.GeodataLoader = patchConfig.GeodataLoader
targetConfig.Profile.StoreSelected = false
targetConfig.GeoXUrl = patchConfig.GeoXUrl
targetConfig.GlobalUA = patchConfig.GlobalUA
if configParams.TestURL != nil {
constant.DefaultTestURL = *configParams.TestURL
}
for idx := range targetConfig.ProxyGroup {
targetConfig.ProxyGroup[idx]["url"] = ""
}
attachHosts(targetConfig.Hosts, patchConfig.Hosts)
if configParams.OverrideDns {
updatePatchDns(patchConfig.DNS)
targetConfig.DNS = patchConfig.DNS
} else {
if targetConfig.DNS.Enable == false {
targetConfig.DNS.Enable = true
}
}
if configParams.OverrideRule {
targetConfig.Rule = overrideRules(patchConfig.Rule, []string{})
} else {
targetConfig.Rule = overrideRules(targetConfig.Rule, patchConfig.Rule)
}
}
func patchConfig() {
log.Infoln("[Apply] patch")
general := currentConfig.General
controller := currentConfig.Controller
tls := currentConfig.TLS
tunnel.SetSniffing(general.Sniffing)
tunnel.SetFindProcessMode(general.FindProcessMode)
dialer.SetTcpConcurrent(general.TCPConcurrent)
dialer.DefaultInterface.Store(general.Interface)
adapter.UnifiedDelay.Store(general.UnifiedDelay)
tunnel.SetMode(general.Mode)
log.SetLevel(general.LogLevel)
resolver.DisableIPv6 = !general.IPv6
route.ReCreateServer(&route.Config{
Addr: controller.ExternalController,
TLSAddr: controller.ExternalControllerTLS,
UnixAddr: controller.ExternalControllerUnix,
PipeAddr: controller.ExternalControllerPipe,
Secret: controller.Secret,
Certificate: tls.Certificate,
PrivateKey: tls.PrivateKey,
DohServer: controller.ExternalDohServer,
IsDebug: false,
Cors: route.Cors{
AllowOrigins: controller.Cors.AllowOrigins,
AllowPrivateNetwork: controller.Cors.AllowPrivateNetwork,
},
})
}
func updateListeners(force bool) {
func updateListeners() {
if !isRunning {
return
}
general := currentConfig.General
listeners := currentConfig.Listeners
if force == true {
stopListeners()
if currentConfig == nil {
return
}
listeners := currentConfig.Listeners
general := currentConfig.General
listener.PatchInboundListeners(listeners, tunnel.Tunnel, true)
listener.SetAllowLan(general.AllowLan)
inbound.SetSkipAuthPrefixes(general.SkipAuthPrefixes)
@@ -326,11 +136,7 @@ func stopListeners() {
listener.StopListener()
}
func patchSelectGroup() {
mapping := configParams.SelectedMap
if mapping == nil {
return
}
func patchSelectGroup(mapping map[string]string) {
for name, proxy := range tunnel.ProxiesWithProviders() {
outbound, ok := proxy.(*adapter.Proxy)
if !ok {
@@ -351,20 +157,102 @@ func patchSelectGroup() {
}
}
func applyConfig(rawConfig *config.RawConfig) error {
func defaultSetupParams() *SetupParams {
return &SetupParams{
Config: config.DefaultRawConfig(),
TestURL: "https://www.gstatic.com/generate_204",
SelectedMap: map[string]string{},
}
}
func readFile(path string) ([]byte, error) {
if _, err := os.Stat(path); os.IsNotExist(err) {
return nil, err
}
data, err := os.ReadFile(path)
if err != nil {
return nil, err
}
return data, err
}
func updateConfig(params *UpdateParams) {
runLock.Lock()
defer runLock.Unlock()
general := currentConfig.General
if params.MixedPort != nil {
general.MixedPort = *params.MixedPort
}
if params.Sniffing != nil {
general.Sniffing = *params.Sniffing
tunnel.SetSniffing(general.Sniffing)
}
if params.FindProcessMode != nil {
general.FindProcessMode = *params.FindProcessMode
tunnel.SetFindProcessMode(general.FindProcessMode)
}
if params.TCPConcurrent != nil {
general.TCPConcurrent = *params.TCPConcurrent
dialer.SetTcpConcurrent(general.TCPConcurrent)
}
if params.Interface != nil {
general.Interface = *params.Interface
dialer.DefaultInterface.Store(general.Interface)
}
if params.UnifiedDelay != nil {
general.UnifiedDelay = *params.UnifiedDelay
adapter.UnifiedDelay.Store(general.UnifiedDelay)
}
if params.Mode != nil {
general.Mode = *params.Mode
tunnel.SetMode(general.Mode)
}
if params.LogLevel != nil {
general.LogLevel = *params.LogLevel
log.SetLevel(general.LogLevel)
}
if params.IPv6 != nil {
general.IPv6 = *params.IPv6
resolver.DisableIPv6 = !general.IPv6
}
if params.ExternalController != nil {
currentConfig.Controller.ExternalController = *params.ExternalController
route.ReCreateServer(&route.Config{
Addr: currentConfig.Controller.ExternalController,
})
}
if params.Tun != nil {
general.Tun.Enable = params.Tun.Enable
general.Tun.AutoRoute = *params.Tun.AutoRoute
general.Tun.Device = *params.Tun.Device
general.Tun.RouteAddress = *params.Tun.RouteAddress
general.Tun.DNSHijack = *params.Tun.DNSHijack
general.Tun.Stack = *params.Tun.Stack
}
updateListeners()
}
func setupConfig(params *SetupParams) error {
runLock.Lock()
defer runLock.Unlock()
var err error
currentConfig, err = config.ParseRawConfig(rawConfig)
constant.DefaultTestURL = params.TestURL
currentConfig, err = config.ParseRawConfig(params.Config)
if err != nil {
currentConfig, _ = config.ParseRawConfig(config.DefaultRawConfig())
}
if configParams.IsPatch {
patchConfig()
} else {
hub.ApplyConfig(currentConfig)
patchSelectGroup()
}
updateListeners(false)
hub.ApplyConfig(currentConfig)
patchSelectGroup(params.SelectedMap)
updateListeners()
return err
}
func UnmarshalJson(data []byte, v any) error {
decoder := json.NewDecoder(b.NewReader(data))
decoder.UseNumber()
err := decoder.Decode(v)
return err
}

View File

@@ -3,7 +3,12 @@ package main
import (
"encoding/json"
"github.com/metacubex/mihomo/adapter/provider"
P "github.com/metacubex/mihomo/component/process"
"github.com/metacubex/mihomo/config"
"github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/log"
"github.com/metacubex/mihomo/tunnel"
"net/netip"
"time"
)
@@ -12,19 +17,34 @@ type InitParams struct {
Version int `json:"version"`
}
type ConfigExtendedParams struct {
IsPatch bool `json:"is-patch"`
IsCompatible bool `json:"is-compatible"`
SelectedMap map[string]string `json:"selected-map"`
TestURL *string `json:"test-url"`
OverrideDns bool `json:"override-dns"`
OverrideRule bool `json:"override-rule"`
type SetupParams struct {
Config *config.RawConfig `json:"config"`
SelectedMap map[string]string `json:"selected-map"`
TestURL string `json:"test-url"`
}
type GenerateConfigParams struct {
ProfileId string `json:"profile-id"`
Config config.RawConfig `json:"config" `
Params ConfigExtendedParams `json:"params"`
type UpdateParams struct {
Tun *tunSchema `json:"tun"`
AllowLan *bool `json:"allow-lan"`
MixedPort *int `json:"mixed-port"`
FindProcessMode *P.FindProcessMode `json:"find-process-mode"`
Mode *tunnel.TunnelMode `json:"mode"`
LogLevel *log.LogLevel `json:"log-level"`
IPv6 *bool `json:"ipv6"`
Sniffing *bool `json:"sniffing"`
TCPConcurrent *bool `json:"tcp-concurrent"`
ExternalController *string `json:"external-controller"`
Interface *string `json:"interface-name"`
UnifiedDelay *bool `json:"unified-delay"`
}
type tunSchema struct {
Enable bool `yaml:"enable" json:"enable"`
Device *string `yaml:"device" json:"device"`
Stack *constant.TUNStack `yaml:"stack" json:"stack"`
DNSHijack *[]string `yaml:"dns-hijack" json:"dns-hijack"`
AutoRoute *bool `yaml:"auto-route" json:"auto-route"`
RouteAddress *[]netip.Prefix `yaml:"route-address" json:"route-address,omitempty"`
}
type ChangeProxyParams struct {
@@ -64,6 +84,7 @@ const (
asyncTestDelayMethod Method = "asyncTestDelay"
getConnectionsMethod Method = "getConnections"
closeConnectionsMethod Method = "closeConnections"
resetConnectionsMethod Method = "resetConnectionsMethod"
closeConnectionMethod Method = "closeConnection"
getExternalProvidersMethod Method = "getExternalProviders"
getExternalProviderMethod Method = "getExternalProvider"
@@ -81,8 +102,9 @@ const (
getAndroidVpnOptionsMethod Method = "getAndroidVpnOptions"
getRunTimeMethod Method = "getRunTime"
getCurrentProfileNameMethod Method = "getCurrentProfileName"
getProfileMethod Method = "getProfile"
crashMethod Method = "crash"
setupConfigMethod Method = "setupConfig"
getConfigMethod Method = "getConfig"
)
type Method string

View File

@@ -6,13 +6,12 @@ replace github.com/metacubex/mihomo => ./Clash.Meta
require (
github.com/metacubex/mihomo v0.0.0-00010101000000-000000000000
github.com/samber/lo v1.49.1
golang.org/x/sync v0.11.0
)
require (
github.com/3andne/restls-client-go v0.1.6 // indirect
github.com/RyuaNerin/go-krypto v1.2.4 // 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
github.com/andybalholm/brotli v1.0.6 // indirect
@@ -21,13 +20,13 @@ require (
github.com/cloudflare/circl v1.3.7 // indirect
github.com/coreos/go-iptables v0.8.0 // indirect
github.com/dlclark/regexp2 v1.11.5 // indirect
github.com/ebitengine/purego v0.8.2 // indirect
github.com/ebitengine/purego v0.8.3 // indirect
github.com/enfein/mieru/v3 v3.13.0 // indirect
github.com/ericlagergren/aegis v0.0.0-20230312195928-b4ce538b56f9 // 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.7.0 // 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.1 // indirect
github.com/go-chi/render v1.0.3 // indirect
@@ -36,7 +35,7 @@ require (
github.com/gobwas/httphead v0.1.0 // indirect
github.com/gobwas/pool v0.2.1 // indirect
github.com/gobwas/ws v1.4.0 // indirect
github.com/gofrs/uuid/v5 v5.3.1 // indirect
github.com/gofrs/uuid/v5 v5.3.2 // 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
@@ -51,22 +50,27 @@ require (
github.com/mdlayher/netlink v1.7.2 // indirect
github.com/mdlayher/socket v0.4.1 // indirect
github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab // indirect
github.com/metacubex/bart v0.19.0 // indirect
github.com/metacubex/bart v0.20.5 // indirect
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399 // indirect
github.com/metacubex/chacha v0.1.2 // 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-20250324165734-5857f47bd43b // indirect
github.com/metacubex/quic-go v0.49.1-0.20250212162123-c135a4412996 // indirect
github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793 // indirect
github.com/metacubex/quic-go v0.52.1-0.20250522021943-aef454b9e639 // indirect
github.com/metacubex/randv2 v0.2.0 // indirect
github.com/metacubex/sing-quic v0.0.0-20250404030904-b2cc8aab562c // indirect
github.com/metacubex/sing-shadowsocks v0.2.8 // indirect
github.com/metacubex/sing-shadowsocks2 v0.2.2 // indirect
github.com/metacubex/sing-shadowtls v0.0.0-20250412122235-0e9005731a63 // indirect
github.com/metacubex/sing-tun v0.4.6-0.20250412144348-c426cb167db5 // indirect
github.com/metacubex/sing-vmess v0.1.14-0.20250228002636-abc39e113b82 // indirect
github.com/metacubex/sing-wireguard v0.0.0-20241126021510-0827d417b589 // indirect
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422 // indirect
github.com/metacubex/utls v1.7.0-alpha.1 // indirect
github.com/metacubex/sing v0.5.3 // indirect
github.com/metacubex/sing-mux v0.3.2 // indirect
github.com/metacubex/sing-quic v0.0.0-20250523120938-f1a248e5ec7f // indirect
github.com/metacubex/sing-shadowsocks v0.2.10 // indirect
github.com/metacubex/sing-shadowsocks2 v0.2.4 // indirect
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 // indirect
github.com/metacubex/sing-tun v0.4.6-0.20250524142129-9d110c0af70c // indirect
github.com/metacubex/sing-vmess v0.2.2 // 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/tfo-go v0.0.0-20250516165257-e29c16ae41d4 // indirect
github.com/metacubex/utls v1.7.3 // indirect
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181 // indirect
github.com/miekg/dns v1.1.63 // indirect
github.com/mroth/weightedrand/v2 v2.1.0 // indirect
@@ -78,14 +82,9 @@ require (
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/puzpuzpuz/xsync/v3 v3.5.1 // indirect
github.com/quic-go/qpack v0.4.0 // indirect
github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
github.com/sagernet/cors v1.2.1 // indirect
github.com/sagernet/fswatch v0.1.1 // indirect
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect
github.com/sagernet/nftables v0.3.0-beta.4 // indirect
github.com/sagernet/sing v0.5.2 // indirect
github.com/sagernet/sing-mux v0.2.1 // indirect
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 // indirect
github.com/samber/lo v1.50.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/rabaead v0.0.0-20220730151906-ab6e06b96e8c // indirect

View File

@@ -1,8 +1,8 @@
github.com/3andne/restls-client-go v0.1.6 h1:tRx/YilqW7iHpgmEL4E1D8dAsuB0tFF3uvncS+B6I08=
github.com/3andne/restls-client-go v0.1.6/go.mod h1:iEdTZNt9kzPIxjIGSMScUFSBrUH6bFRNg0BWlP4orEY=
github.com/RyuaNerin/elliptic2 v1.0.0/go.mod h1:wWB8fWrJI/6EPJkyV/r1Rj0hxUgrusmqSj8JN6yNf/A=
github.com/RyuaNerin/go-krypto v1.2.4 h1:mXuNdK6M317aPV0llW6Xpjbo4moOlPF7Yxz4tb4b4Go=
github.com/RyuaNerin/go-krypto v1.2.4/go.mod h1:QqCYkoutU3yInyD9INt2PGolVRsc3W4oraQadVGXJ/8=
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=
github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 h1:cDVUiFo+npB0ZASqnw4q90ylaVAbnYyx0JYqK4YcGok=
github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344/go.mod h1:9pIqrY6SXNL8vjRQE5Hd/OL5GyK/9MrGUWs87z/eFfk=
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
@@ -26,12 +26,12 @@ 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.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I=
github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/ebitengine/purego v0.8.3 h1:K+0AjQp63JEZTEMZiwsI9g0+hAMNohwUOtY0RPGexmc=
github.com/ebitengine/purego v0.8.3/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/enfein/mieru/v3 v3.13.0 h1:eGyxLGkb+lut9ebmx+BGwLJ5UMbEc/wGIYO0AXEKy98=
github.com/enfein/mieru/v3 v3.13.0/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM=
github.com/ericlagergren/aegis v0.0.0-20230312195928-b4ce538b56f9 h1:/5RkVc9Rc81XmMyVqawCiDyrBHZbLAZgTTCqou4mwj8=
github.com/ericlagergren/aegis v0.0.0-20230312195928-b4ce538b56f9/go.mod h1:hkIFzoiIPZYxdFOOLyDho59b7SrDfo+w3h+yWdlg45I=
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=
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391/go.mod h1:K2R7GhgxrlJzHw2qiPWsCZXf/kXEJN9PLnQK73Ll0po=
github.com/ericlagergren/saferand v0.0.0-20220206064634-960a4dd2bc5c h1:RUzBDdZ+e/HEe2Nh8lYsduiPAZygUfVXJn0Ncj5sHMg=
@@ -39,8 +39,8 @@ github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1 h1:tlDMEdcPRQKBE
github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1/go.mod h1:4RfsapbGx2j/vU5xC/5/9qB3kn9Awp1YDiEnN43QrJ4=
github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010 h1:fuGucgPk5dN6wzfnxl3D0D3rVLw4v2SbBT9jb4VnxzA=
github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010/go.mod h1:JtBcj7sBuTTRupn7c2bFspMDIObMJsVK8TeUvpShPok=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
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.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
@@ -59,8 +59,8 @@ 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/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs=
github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc=
github.com/gofrs/uuid/v5 v5.3.1 h1:aPx49MwJbekCzOyhZDjJVb0hx3A0KLjlbLx6p2gY0p0=
github.com/gofrs/uuid/v5 v5.3.1/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
github.com/gofrs/uuid/v5 v5.3.2 h1:2jfO8j3XgSwlz/wHqemAEugfnTlikAYHhnqQ8Xh4fE0=
github.com/gofrs/uuid/v5 v5.3.2/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/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
@@ -97,38 +97,49 @@ github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab h1:Chbw+/31UC14YFNr78pESt5Vowlc62zziw05JCUqoL4=
github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab/go.mod h1:xVKK8jC5Sd3hfh7WjmCq+HorehIbrBijaUWmcuKjPcI=
github.com/metacubex/bart v0.19.0 h1:XQ9AJeI+WO+phRPkUOoflAFwlqDJnm5BPQpixciJQBY=
github.com/metacubex/bart v0.19.0/go.mod h1:DCcyfP4MC+Zy7sLK7XeGuMw+P5K9mIRsYOBgiE8icsI=
github.com/metacubex/bart v0.20.5 h1:XkgLZ17QxfxkqKdGsojoM2Zu01mmHyyQSFzt2/calTM=
github.com/metacubex/bart v0.20.5/go.mod h1:DCcyfP4MC+Zy7sLK7XeGuMw+P5K9mIRsYOBgiE8icsI=
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399 h1:oBowHVKZycNtAFbZ6avaCSZJYeme2Nrj+4RpV2cNJig=
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399/go.mod h1:4xcieuIK+M4bGQmQYZVqEaIYqjS1ahO4kXG7EmDgEro=
github.com/metacubex/chacha v0.1.2 h1:QulCq3eVm3TO6+4nVIWJtmSe7BT2GMrgVHuAoqRQnlc=
github.com/metacubex/chacha v0.1.2/go.mod h1:Djn9bPZxLTXbJFSeyo0/qzEzQI+gUSSzttuzZM75GH8=
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-20250324165734-5857f47bd43b h1:RUh4OdVPz/jDrM9MQ2ySuqu2aeBqcA8rtfWUYLZ8RtI=
github.com/metacubex/gvisor v0.0.0-20250324165734-5857f47bd43b/go.mod h1:8LpS0IJW1VmWzUm3ylb0e2SK5QDm5lO/2qwWLZgRpBU=
github.com/metacubex/quic-go v0.49.1-0.20250212162123-c135a4412996 h1:B+AP/Pj2/jBDS/kCYjz/x+0BCOKfd2VODYevyeIt+Ds=
github.com/metacubex/quic-go v0.49.1-0.20250212162123-c135a4412996/go.mod h1:ExVjGyEwTUjCFqx+5uxgV7MOoA3fZI+th4D40H35xmY=
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.52.1-0.20250522021943-aef454b9e639 h1:L+1brQNzBhCCxWlicwfK1TlceemCRmrDE4HmcVHc29w=
github.com/metacubex/quic-go v0.52.1-0.20250522021943-aef454b9e639/go.mod h1:Kc6h++Q/zf3AxcUCevJhJwgrskJumv+pZdR8g/E/10k=
github.com/metacubex/randv2 v0.2.0 h1:uP38uBvV2SxYfLj53kuvAjbND4RUDfFJjwr4UigMiLs=
github.com/metacubex/randv2 v0.2.0/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFqSPyj2cxY=
github.com/metacubex/sing-quic v0.0.0-20250404030904-b2cc8aab562c h1:OB3WmMA8YPJjE36RjD9X8xlrWGJ4orxbf2R/KAE28b0=
github.com/metacubex/sing-quic v0.0.0-20250404030904-b2cc8aab562c/go.mod h1:g7Mxj7b7zm7YVqD975mk/hSmrb0A0G4bVvIMr2MMzn8=
github.com/metacubex/sing-shadowsocks v0.2.8 h1:wIhlaigswzjPw4hej75sEvWte3QR0+AJRafgwBHO5B4=
github.com/metacubex/sing-shadowsocks v0.2.8/go.mod h1:X3x88XtJpBxG0W0/ECOJL6Ib0SJ3xdniAkU/6/RMWU0=
github.com/metacubex/sing-shadowsocks2 v0.2.2 h1:eaf42uVx4Lr21S6MDYs0ZdTvGA0GEhDpb9no4+gdXPo=
github.com/metacubex/sing-shadowsocks2 v0.2.2/go.mod h1:BhOug03a/RbI7y6hp6q+6ITM1dXjnLTmeWBHSTwvv2Q=
github.com/metacubex/sing-shadowtls v0.0.0-20250412122235-0e9005731a63 h1:vy/8ZYYtWUXYnOnw/NF8ThG1W/RqM/h5rkun+OXZMH0=
github.com/metacubex/sing-shadowtls v0.0.0-20250412122235-0e9005731a63/go.mod h1:eDZ2JpkSkewGmUlCoLSn2MRFn1D0jKPIys/6aogFx7U=
github.com/metacubex/sing-tun v0.4.6-0.20250412144348-c426cb167db5 h1:hcsz5e5lqhBxn3iQQDIF60FLZ8PQT542GTQZ+1wcIGo=
github.com/metacubex/sing-tun v0.4.6-0.20250412144348-c426cb167db5/go.mod h1:V0N4rr0dWPBEE20ESkTXdbtx2riQYcb6YtwC5w/9wl0=
github.com/metacubex/sing-vmess v0.1.14-0.20250228002636-abc39e113b82 h1:zZp5uct9+/0Hb1jKGyqDjCU4/72t43rs7qOq3Rc9oU8=
github.com/metacubex/sing-vmess v0.1.14-0.20250228002636-abc39e113b82/go.mod h1:nE7Mdzj/QUDwgRi/8BASPtsxtIFZTHA4Yst5GgwbGCQ=
github.com/metacubex/sing-wireguard v0.0.0-20241126021510-0827d417b589 h1:Z6bNy0HLTjx6BKIkV48sV/yia/GP8Bnyb5JQuGgSGzg=
github.com/metacubex/sing-wireguard v0.0.0-20241126021510-0827d417b589/go.mod h1:4NclTLIZuk+QkHVCGrP87rHi/y8YjgPytxTgApJNMhc=
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422 h1:zGeQt3UyNydIVrMRB97AA5WsYEau/TyCnRtTf1yUmJY=
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
github.com/metacubex/utls v1.7.0-alpha.1 h1:oMFsPh2oTlALJ7vKXPJuqgy0YeiZ+q/LLw+ZdxZ80l4=
github.com/metacubex/utls v1.7.0-alpha.1/go.mod h1:oknYT0qTOwE4hjPmZOEpzVdefnW7bAdGLvZcqmk4TLU=
github.com/metacubex/sing v0.5.2/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w=
github.com/metacubex/sing v0.5.3 h1:QWdN16WFKMk06x4nzkc8SvZ7y2x+TLQrpkPoHs+WSVM=
github.com/metacubex/sing v0.5.3/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w=
github.com/metacubex/sing-mux v0.3.2 h1:nJv52pyRivHcaZJKk2JgxpaVvj1GAXG81scSa9N7ncw=
github.com/metacubex/sing-mux v0.3.2/go.mod h1:3rt1soewn0O6j89GCLmwAQFsq257u0jf2zQSPhTL3Bw=
github.com/metacubex/sing-quic v0.0.0-20250523120938-f1a248e5ec7f h1:mP3vIm+9hRFI0C0Vl3pE0NESF/L85FDbuB0tGgUii6I=
github.com/metacubex/sing-quic v0.0.0-20250523120938-f1a248e5ec7f/go.mod h1:JPTpf7fpnojsSuwRJExhSZSy63pVbp3VM39+zj+sAJM=
github.com/metacubex/sing-shadowsocks v0.2.10 h1:Pr7LDbjMANIQHl07zWgl1vDuhpsfDQUpZ8cX6DPabfg=
github.com/metacubex/sing-shadowsocks v0.2.10/go.mod h1:MtRM0ZZjR0kaDOzy9zWSt6/4/UlrnsNBq+1FNAF4vBk=
github.com/metacubex/sing-shadowsocks2 v0.2.4 h1:Ec0x3hHR7xkld5Z09IGh16wtUUpBb2HgqZ9DExd8Q7s=
github.com/metacubex/sing-shadowsocks2 v0.2.4/go.mod h1:WP8+S0kqtnSbX1vlIpo5i8Irm/ijZITEPBcZ26B5unY=
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.6-0.20250524142129-9d110c0af70c h1:Y6jk7AH5BEg9Dsvczrf/KokYsvxeKSZZlCLHg+hC4ro=
github.com/metacubex/sing-tun v0.4.6-0.20250524142129-9d110c0af70c/go.mod h1:HDaHDL6onAX2ZGbAGUXKp++PohRdNb7Nzt6zxzhox+U=
github.com/metacubex/sing-vmess v0.2.2 h1:nG6GIKF1UOGmlzs+BIetdGHkFZ20YqFVIYp5Htqzp+4=
github.com/metacubex/sing-vmess v0.2.2/go.mod h1:CVDNcdSLVYFgTHQlubr88d8CdqupAUDqLjROos+H9xk=
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-20250503055512-501391591dee h1:lp6hJ+4wCLZu113awp7P6odM2okB5s60HUyF0FMqKmo=
github.com/metacubex/smux v0.0.0-20250503055512-501391591dee/go.mod h1:4bPD8HWx9jPJ9aE4uadgyN7D1/Wz3KmPy+vale8sKLE=
github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4 h1:j1VRTiC9JLR4nUbSikx9OGdu/3AgFDqgcLj4GoqyQkc=
github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
github.com/metacubex/utls v1.7.3 h1:yDcMEWojFh+t8rU9X0HPcZDPAoFze/rIIyssqivzj8A=
github.com/metacubex/utls v1.7.3/go.mod h1:oknYT0qTOwE4hjPmZOEpzVdefnW7bAdGLvZcqmk4TLU=
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181 h1:hJLQviGySBuaynlCwf/oYgIxbVbGRUIKZCxdya9YrbQ=
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181/go.mod h1:phewKljNYiTVT31Gcif8RiCKnTUOgVWFJjccqYM8s+Y=
github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY=
@@ -159,25 +170,12 @@ github.com/puzpuzpuz/xsync/v3 v3.5.1 h1:GJYJZwO6IdxN/IKbneznS6yPkVC+c3zyY/j19c++
github.com/puzpuzpuz/xsync/v3 v3.5.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
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/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs=
github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
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/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs=
github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o=
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/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I=
github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8=
github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo=
github.com/sagernet/sing v0.5.2 h1:2OZQJNKGtji/66QLxbf/T/dqtK/3+fF/zuHH9tsGK7M=
github.com/sagernet/sing v0.5.2/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing-mux v0.2.1 h1:N/3MHymfnFZRd29tE3TaXwPUVVgKvxhtOkiCMLp9HVo=
github.com/sagernet/sing-mux v0.2.1/go.mod h1:dm3BWL6NvES9pbib7llpylrq7Gq+LjlzG+0RacdxcyE=
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ=
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7/go.mod h1:FP9X2xjT/Az1EsG/orYYoC+5MojWnuI7hrffz8fGwwo=
github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew=
github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o=
github.com/samber/lo v1.50.0 h1:XrG0xOeHs+4FQ8gJR97zDz5uOFMW7OwFWiFVzqopKgY=
github.com/samber/lo v1.50.0/go.mod h1:RjZyNk6WSnUFRKK6EyOhsRJMqft3G+pg7dCWHQCWvsc=
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=
@@ -189,9 +187,16 @@ 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.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
@@ -251,7 +256,7 @@ golang.org/x/sys v0.1.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.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
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=

View File

@@ -10,6 +10,7 @@ import (
"github.com/metacubex/mihomo/common/observable"
"github.com/metacubex/mihomo/common/utils"
"github.com/metacubex/mihomo/component/mmdb"
"github.com/metacubex/mihomo/component/resolver"
"github.com/metacubex/mihomo/component/updater"
"github.com/metacubex/mihomo/config"
"github.com/metacubex/mihomo/constant"
@@ -28,10 +29,8 @@ import (
var (
isInit = false
configParams = ConfigExtendedParams{}
externalProviders = map[string]cp.Provider{}
logSubscriber observable.Subscription[log.Event]
currentConfig *config.Config
)
func handleInitClash(paramsString string) bool {
@@ -52,7 +51,8 @@ func handleStartListener() bool {
runLock.Lock()
defer runLock.Unlock()
isRunning = true
updateListeners(true)
updateListeners()
resolver.ResetConnection()
return true
}
@@ -91,30 +91,10 @@ func handleValidateConfig(bytes []byte) string {
return ""
}
func handleUpdateConfig(bytes []byte) string {
var params = &GenerateConfigParams{}
err := json.Unmarshal(bytes, params)
if err != nil {
return err.Error()
}
configParams = params.Params
prof := decorationConfig(params.ProfileId, params.Config)
err = applyConfig(prof)
if err != nil {
return err.Error()
}
return ""
}
func handleGetProxies() string {
func handleGetProxies() map[string]constant.Proxy {
runLock.Lock()
defer runLock.Unlock()
data, err := json.Marshal(tunnel.ProxiesWithProviders())
if err != nil {
return ""
}
return string(data)
return tunnel.ProxiesWithProviders()
}
func handleChangeProxy(data string, fn func(string string)) {
@@ -184,21 +164,12 @@ func handleGetTotalTraffic() string {
return string(data)
}
func handleGetProfile(profileId string) string {
prof := getRawConfigWithId(profileId)
data, err := json.Marshal(prof)
if err != nil {
return ""
}
return string(data)
}
func handleResetTraffic() {
statistic.DefaultManager.ResetStatistic()
}
func handleAsyncTestDelay(paramsString string, fn func(string)) {
b.Go(paramsString, func() (bool, error) {
mBatch.Go(paramsString, func() (bool, error) {
var params = &TestDelayParams{}
err := json.Unmarshal([]byte(paramsString), params)
if err != nil {
@@ -266,6 +237,11 @@ func handleGetConnections() string {
func handleCloseConnections() bool {
runLock.Lock()
defer runLock.Unlock()
closeConnections()
return true
}
func closeConnections() {
statistic.DefaultManager.Range(func(c statistic.Tracker) bool {
err := c.Close()
if err != nil {
@@ -273,6 +249,12 @@ func handleCloseConnections() bool {
}
return true
})
}
func handleResetConnections() bool {
runLock.Lock()
defer runLock.Unlock()
resolver.ResetConnection()
return true
}
@@ -442,10 +424,47 @@ func handleSetState(params string) {
_ = json.Unmarshal([]byte(params), state.CurrentState)
}
func handleGetConfig(path string) (*config.RawConfig, error) {
bytes, err := readFile(path)
if err != nil {
return nil, err
}
prof, err := config.UnmarshalRawConfig(bytes)
if err != nil {
return nil, err
}
return prof, nil
}
func handleCrash() {
panic("handle invoke crash")
}
func handleUpdateConfig(bytes []byte) string {
var params = &UpdateParams{}
err := json.Unmarshal(bytes, params)
if err != nil {
return err.Error()
}
updateConfig(params)
return ""
}
func handleSetupConfig(bytes []byte) string {
var params = defaultSetupParams()
err := UnmarshalJson(bytes, params)
if err != nil {
log.Errorln("unmarshalRawConfig error %v", err)
_ = setupConfig(defaultSetupParams())
return err.Error()
}
err = setupConfig(params)
if err != nil {
return err.Error()
}
return ""
}
func init() {
adapter.UrlTestHook = func(url string, name string, delay uint16) {
delayData := &Delay{

View File

@@ -39,6 +39,14 @@ func freeCString(s *C.char) {
C.free(unsafe.Pointer(s))
}
func (result ActionResult) send() {
data, err := result.Json()
if err != nil {
return
}
bridge.SendToPort(result.Port, string(data))
}
//export invokeAction
func invokeAction(paramsChar *C.char, port C.longlong) {
params := C.GoString(paramsChar)
@@ -49,22 +57,38 @@ func invokeAction(paramsChar *C.char, port C.longlong) {
bridge.SendToPort(i, err.Error())
return
}
go handleAction(action, func(data interface{}) {
bridge.SendToPort(i, string(action.getResult(data)))
})
result := ActionResult{
Id: action.Id,
Method: action.Method,
Port: i,
}
go handleAction(action, result)
}
func sendMessage(message Message) {
if messagePort == -1 {
return
}
res, err := message.Json()
if err != nil {
return
}
bridge.SendToPort(messagePort, string(Action{
result := ActionResult{
Method: messageMethod,
}.getResult(res)))
Port: messagePort,
Data: message,
}
result.send()
}
//export getConfig
func getConfig(s *C.char) *C.char {
path := C.GoString(s)
config, err := handleGetConfig(path)
if err != nil {
return C.CString("")
}
marshal, err := json.Marshal(config)
if err != nil {
return C.CString("")
}
return C.CString(string(marshal))
}
//export startListener

View File

@@ -58,7 +58,7 @@ func (t *TunHandler) handleProtect(fd int) {
return
}
protect(t.callback, fd)
Protect(t.callback, fd)
}
func (t *TunHandler) handleResolveProcess(source, target net.Addr) string {
@@ -79,7 +79,7 @@ func (t *TunHandler) handleResolveProcess(source, target net.Addr) string {
if version < 29 {
uid = platform.QuerySocketUidFromProcFs(source, target)
}
return resolveProcess(t.callback, protocol, source.String(), target.String(), uid)
return ResolveProcess(t.callback, protocol, source.String(), target.String(), uid)
}
var (
@@ -99,27 +99,25 @@ func handleStopTun() {
}
func handleStartTun(fd int, callback unsafe.Pointer) {
go func() {
handleStopTun()
tunLock.Lock()
defer tunLock.Unlock()
now := time.Now()
runTime = &now
if fd != 0 {
tunHandler = &TunHandler{
callback: callback,
limit: semaphore.NewWeighted(4),
}
initTunHook()
tunListener, _ := t.Start(fd, currentConfig.General.Tun.Device, currentConfig.General.Tun.Stack)
if tunListener != nil {
log.Infoln("TUN address: %v", tunListener.Address())
tunHandler.listener = tunListener
} else {
removeTunHook()
}
handleStopTun()
tunLock.Lock()
defer tunLock.Unlock()
now := time.Now()
runTime = &now
if fd != 0 {
tunHandler = &TunHandler{
callback: callback,
limit: semaphore.NewWeighted(4),
}
}()
initTunHook()
tunListener, _ := t.Start(fd, currentConfig.General.Tun.Device, currentConfig.General.Tun.Stack)
if tunListener != nil {
log.Infoln("TUN address: %v", tunListener.Address())
tunHandler.listener = tunListener
} else {
removeTunHook()
}
}
}
func handleGetRunTime() string {
@@ -190,21 +188,21 @@ func handleGetCurrentProfileName() string {
return state.CurrentState.CurrentProfileName
}
func nextHandle(action *Action, result func(data interface{})) bool {
func nextHandle(action *Action, result ActionResult) bool {
switch action.Method {
case getAndroidVpnOptionsMethod:
result(handleGetAndroidVpnOptions())
result.success(handleGetAndroidVpnOptions())
return true
case updateDnsMethod:
data := action.Data.(string)
handleUpdateDns(data)
result(true)
result.success(true)
return true
case getRunTimeMethod:
result(handleGetRunTime())
result.success(handleGetRunTime())
return true
case getCurrentProfileNameMethod:
result(handleGetCurrentProfileName())
result.success(handleGetCurrentProfileName())
return true
}
return false
@@ -222,7 +220,7 @@ func quickStart(initParamsChar *C.char, paramsChar *C.char, stateParamsChar *C.c
bridge.SendToPort(i, "init error")
}
handleSetState(stateParams)
bridge.SendToPort(i, handleUpdateConfig(bytes))
bridge.SendToPort(i, handleSetupConfig(bytes))
}()
}

View File

@@ -12,14 +12,20 @@ import (
var conn net.Conn
func sendMessage(message Message) {
res, err := message.Json()
func (result ActionResult) send() {
data, err := result.Json()
if err != nil {
return
}
send(Action{
send(data)
}
func sendMessage(message Message) {
result := ActionResult{
Method: messageMethod,
}.getResult(res))
Data: message,
}
result.send()
}
func send(data []byte) {
@@ -61,12 +67,15 @@ func startServer(arg string) {
return
}
go handleAction(action, func(data interface{}) {
send(action.getResult(data))
})
result := ActionResult{
Id: action.Id,
Method: action.Method,
}
go handleAction(action, result)
}
}
func nextHandle(action *Action, result func(data interface{})) bool {
func nextHandle(action *Action, result ActionResult) bool {
return false
}

View File

@@ -1,5 +1,6 @@
import 'dart:async';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:fl_clash/clash/clash.dart';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/l10n/l10n.dart';
@@ -100,7 +101,10 @@ class ApplicationState extends ConsumerState<Application> {
return AppStateManager(
child: ClashManager(
child: ConnectivityManager(
onConnectivityChanged: () {
onConnectivityChanged: (results) async {
if (!results.contains(ConnectivityResult.vpn)) {
await clashCore.closeConnections();
}
globalState.appController.updateLocalIp();
globalState.appController.addCheckIpNumDebounce();
},

View File

@@ -66,6 +66,11 @@ class ClashCore {
Future<bool> init() async {
await initGeo();
if (globalState.config.appSetting.openLogs) {
clashCore.startLog();
} else {
clashCore.stopLog();
}
final homeDirPath = await appPath.homeDirPath;
return await clashInterface.init(
InitParams(
@@ -89,39 +94,39 @@ class ClashCore {
return clashInterface.validateConfig(data);
}
Future<String> updateConfig(UpdateConfigParams updateConfigParams) async {
return await clashInterface.updateConfig(updateConfigParams);
Future<String> updateConfig(UpdateParams updateParams) async {
return await clashInterface.updateConfig(updateParams);
}
Future<String> setupConfig(SetupParams setupParams) async {
return await clashInterface.setupConfig(setupParams);
}
Future<List<Group>> getProxiesGroups() async {
final proxiesRawString = await clashInterface.getProxies();
return Isolate.run<List<Group>>(() {
if (proxiesRawString.isEmpty) return [];
final proxies = (json.decode(proxiesRawString) ?? {}) as Map;
if (proxies.isEmpty) return [];
final groupNames = [
UsedProxy.GLOBAL.name,
...(proxies[UsedProxy.GLOBAL.name]["all"] as List).where((e) {
final proxy = proxies[e] ?? {};
return GroupTypeExtension.valueList.contains(proxy['type']);
})
];
final groupsRaw = groupNames.map((groupName) {
final group = proxies[groupName];
group["all"] = ((group["all"] ?? []) as List)
.map(
(name) => proxies[name],
)
.where((proxy) => proxy != null)
.toList();
return group;
}).toList();
return groupsRaw
final proxies = await clashInterface.getProxies();
if (proxies.isEmpty) return [];
final groupNames = [
UsedProxy.GLOBAL.name,
...(proxies[UsedProxy.GLOBAL.name]["all"] as List).where((e) {
final proxy = proxies[e] ?? {};
return GroupTypeExtension.valueList.contains(proxy['type']);
})
];
final groupsRaw = groupNames.map((groupName) {
final group = proxies[groupName];
group["all"] = ((group["all"] ?? []) as List)
.map(
(e) => Group.fromJson(e),
(name) => proxies[name],
)
.where((proxy) => proxy != null)
.toList();
});
return group;
}).toList();
return groupsRaw
.map(
(e) => Group.fromJson(e),
)
.toList();
}
FutureOr<String> changeProxy(ChangeProxyParams changeProxyParams) async {
@@ -143,6 +148,10 @@ class ClashCore {
clashInterface.closeConnections();
}
resetConnections() {
clashInterface.resetConnections();
}
Future<List<ExternalProvider>> getExternalProviders() async {
final externalProvidersRawString =
await clashInterface.getExternalProviders();
@@ -206,6 +215,16 @@ class ClashCore {
return Delay.fromJson(json.decode(data));
}
Future<Map<String, dynamic>> getConfig(String id) async {
final profilePath = await appPath.getProfilePath(id);
final res = await clashInterface.getConfig(profilePath);
if (res.isSuccess) {
return res.data as Map<String, dynamic>;
} else {
throw res.message;
}
}
Future<Traffic> getTraffic() async {
final trafficString = await clashInterface.getTraffic();
if (trafficString.isEmpty) {
@@ -241,14 +260,6 @@ class ClashCore {
return int.parse(value);
}
Future<ClashConfigSnippet?> getProfile(String id) async {
final res = await clashInterface.getProfile(id);
if (res.isEmpty) {
return null;
}
return Isolate.run(() => ClashConfigSnippet.fromJson(json.decode(res)));
}
resetTraffic() {
clashInterface.resetTraffic();
}

View File

@@ -154,18 +154,18 @@ class ClashFFI {
late final _setrlimit =
_setrlimitPtr.asFunction<int Function(int, ffi.Pointer<rlimit>)>();
int wait1(
int wait$1(
ffi.Pointer<ffi.Int> arg0,
) {
return _wait1(
return _wait$1(
arg0,
);
}
late final _wait1Ptr =
late final _wait$1Ptr =
_lookup<ffi.NativeFunction<pid_t Function(ffi.Pointer<ffi.Int>)>>('wait');
late final _wait1 =
_wait1Ptr.asFunction<int Function(ffi.Pointer<ffi.Int>)>();
late final _wait$1 =
_wait$1Ptr.asFunction<int Function(ffi.Pointer<ffi.Int>)>();
int waitpid(
int arg0,
@@ -2518,6 +2518,20 @@ class ClashFFI {
late final _invokeAction =
_invokeActionPtr.asFunction<void Function(ffi.Pointer<ffi.Char>, int)>();
ffi.Pointer<ffi.Char> getConfig(
ffi.Pointer<ffi.Char> s,
) {
return _getConfig(
s,
);
}
late final _getConfigPtr = _lookup<
ffi.NativeFunction<
ffi.Pointer<ffi.Char> Function(ffi.Pointer<ffi.Char>)>>('getConfig');
late final _getConfig = _getConfigPtr
.asFunction<ffi.Pointer<ffi.Char> Function(ffi.Pointer<ffi.Char>)>();
void startListener() {
return _startListener();
}
@@ -2639,6 +2653,29 @@ class ClashFFI {
_updateDnsPtr.asFunction<void Function(ffi.Pointer<ffi.Char>)>();
}
typedef __int8_t = ffi.SignedChar;
typedef Dart__int8_t = int;
typedef __uint8_t = ffi.UnsignedChar;
typedef Dart__uint8_t = int;
typedef __int16_t = ffi.Short;
typedef Dart__int16_t = int;
typedef __uint16_t = ffi.UnsignedShort;
typedef Dart__uint16_t = int;
typedef __int32_t = ffi.Int;
typedef Dart__int32_t = int;
typedef __uint32_t = ffi.UnsignedInt;
typedef Dart__uint32_t = int;
typedef __int64_t = ffi.LongLong;
typedef Dart__int64_t = int;
typedef __uint64_t = ffi.UnsignedLongLong;
typedef Dart__uint64_t = int;
typedef __darwin_intptr_t = ffi.Long;
typedef Dart__darwin_intptr_t = int;
typedef __darwin_natural_t = ffi.UnsignedInt;
typedef Dart__darwin_natural_t = int;
typedef __darwin_ct_rune_t = ffi.Int;
typedef Dart__darwin_ct_rune_t = int;
final class __mbstate_t extends ffi.Union {
@ffi.Array.multi([128])
external ffi.Array<ffi.Char> __mbstate8;
@@ -2647,6 +2684,46 @@ final class __mbstate_t extends ffi.Union {
external int _mbstateL;
}
typedef __darwin_mbstate_t = __mbstate_t;
typedef __darwin_ptrdiff_t = ffi.Long;
typedef Dart__darwin_ptrdiff_t = int;
typedef __darwin_size_t = ffi.UnsignedLong;
typedef Dart__darwin_size_t = int;
typedef __builtin_va_list = ffi.Pointer<ffi.Char>;
typedef __darwin_va_list = __builtin_va_list;
typedef __darwin_wchar_t = ffi.Int;
typedef Dart__darwin_wchar_t = int;
typedef __darwin_rune_t = __darwin_wchar_t;
typedef __darwin_wint_t = ffi.Int;
typedef Dart__darwin_wint_t = int;
typedef __darwin_clock_t = ffi.UnsignedLong;
typedef Dart__darwin_clock_t = int;
typedef __darwin_socklen_t = __uint32_t;
typedef __darwin_ssize_t = ffi.Long;
typedef Dart__darwin_ssize_t = int;
typedef __darwin_time_t = ffi.Long;
typedef Dart__darwin_time_t = int;
typedef __darwin_blkcnt_t = __int64_t;
typedef __darwin_blksize_t = __int32_t;
typedef __darwin_dev_t = __int32_t;
typedef __darwin_fsblkcnt_t = ffi.UnsignedInt;
typedef Dart__darwin_fsblkcnt_t = int;
typedef __darwin_fsfilcnt_t = ffi.UnsignedInt;
typedef Dart__darwin_fsfilcnt_t = int;
typedef __darwin_gid_t = __uint32_t;
typedef __darwin_id_t = __uint32_t;
typedef __darwin_ino64_t = __uint64_t;
typedef __darwin_ino_t = __darwin_ino64_t;
typedef __darwin_mach_port_name_t = __darwin_natural_t;
typedef __darwin_mach_port_t = __darwin_mach_port_name_t;
typedef __darwin_mode_t = __uint16_t;
typedef __darwin_off_t = __int64_t;
typedef __darwin_pid_t = __int32_t;
typedef __darwin_sigset_t = __uint32_t;
typedef __darwin_suseconds_t = __int32_t;
typedef __darwin_uid_t = __uint32_t;
typedef __darwin_useconds_t = __uint32_t;
final class __darwin_pthread_handler_rec extends ffi.Struct {
external ffi
.Pointer<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Void>)>>
@@ -2731,6 +2808,48 @@ final class _opaque_pthread_t extends ffi.Struct {
external ffi.Array<ffi.Char> __opaque;
}
typedef __darwin_pthread_attr_t = _opaque_pthread_attr_t;
typedef __darwin_pthread_cond_t = _opaque_pthread_cond_t;
typedef __darwin_pthread_condattr_t = _opaque_pthread_condattr_t;
typedef __darwin_pthread_key_t = ffi.UnsignedLong;
typedef Dart__darwin_pthread_key_t = int;
typedef __darwin_pthread_mutex_t = _opaque_pthread_mutex_t;
typedef __darwin_pthread_mutexattr_t = _opaque_pthread_mutexattr_t;
typedef __darwin_pthread_once_t = _opaque_pthread_once_t;
typedef __darwin_pthread_rwlock_t = _opaque_pthread_rwlock_t;
typedef __darwin_pthread_rwlockattr_t = _opaque_pthread_rwlockattr_t;
typedef __darwin_pthread_t = ffi.Pointer<_opaque_pthread_t>;
typedef __darwin_nl_item = ffi.Int;
typedef Dart__darwin_nl_item = int;
typedef __darwin_wctrans_t = ffi.Int;
typedef Dart__darwin_wctrans_t = int;
typedef __darwin_wctype_t = __uint32_t;
typedef u_int8_t = ffi.UnsignedChar;
typedef Dartu_int8_t = int;
typedef u_int16_t = ffi.UnsignedShort;
typedef Dartu_int16_t = int;
typedef u_int32_t = ffi.UnsignedInt;
typedef Dartu_int32_t = int;
typedef u_int64_t = ffi.UnsignedLongLong;
typedef Dartu_int64_t = int;
typedef register_t = ffi.Int64;
typedef Dartregister_t = int;
typedef user_addr_t = u_int64_t;
typedef user_size_t = u_int64_t;
typedef user_ssize_t = ffi.Int64;
typedef Dartuser_ssize_t = int;
typedef user_long_t = ffi.Int64;
typedef Dartuser_long_t = int;
typedef user_ulong_t = u_int64_t;
typedef user_time_t = ffi.Int64;
typedef Dartuser_time_t = int;
typedef user_off_t = ffi.Int64;
typedef Dartuser_off_t = int;
typedef syscall_arg_t = u_int64_t;
typedef ptrdiff_t = __darwin_ptrdiff_t;
typedef rsize_t = __darwin_size_t;
typedef wint_t = __darwin_wint_t;
final class _GoString_ extends ffi.Struct {
external ffi.Pointer<ffi.Char> p;
@@ -2738,10 +2857,6 @@ final class _GoString_ extends ffi.Struct {
external int n;
}
typedef ptrdiff_t = __darwin_ptrdiff_t;
typedef __darwin_ptrdiff_t = ffi.Long;
typedef Dart__darwin_ptrdiff_t = int;
enum idtype_t {
P_ALL(0),
P_PID(1),
@@ -2754,10 +2869,15 @@ enum idtype_t {
0 => P_ALL,
1 => P_PID,
2 => P_PGID,
_ => throw ArgumentError("Unknown value for idtype_t: $value"),
_ => throw ArgumentError('Unknown value for idtype_t: $value'),
};
}
typedef pid_t = __darwin_pid_t;
typedef id_t = __darwin_id_t;
typedef sig_atomic_t = ffi.Int;
typedef Dartsig_atomic_t = int;
final class __darwin_arm_exception_state extends ffi.Struct {
@__uint32_t()
external int __exception;
@@ -2769,9 +2889,6 @@ final class __darwin_arm_exception_state extends ffi.Struct {
external int __far;
}
typedef __uint32_t = ffi.UnsignedInt;
typedef Dart__uint32_t = int;
final class __darwin_arm_exception_state64 extends ffi.Struct {
@__uint64_t()
external int __far;
@@ -2783,9 +2900,6 @@ final class __darwin_arm_exception_state64 extends ffi.Struct {
external int __exception;
}
typedef __uint64_t = ffi.UnsignedLongLong;
typedef Dart__uint64_t = int;
final class __darwin_arm_exception_state64_v2 extends ffi.Struct {
@__uint64_t()
external int __far;
@@ -2914,6 +3028,9 @@ final class __darwin_mcontext32 extends ffi.Struct {
final class __darwin_mcontext64 extends ffi.Opaque {}
typedef mcontext_t = ffi.Pointer<__darwin_mcontext64>;
typedef pthread_attr_t = __darwin_pthread_attr_t;
final class __darwin_sigaltstack extends ffi.Struct {
external ffi.Pointer<ffi.Void> ss_sp;
@@ -2924,8 +3041,7 @@ final class __darwin_sigaltstack extends ffi.Struct {
external int ss_flags;
}
typedef __darwin_size_t = ffi.UnsignedLong;
typedef Dart__darwin_size_t = int;
typedef stack_t = __darwin_sigaltstack;
final class __darwin_ucontext extends ffi.Struct {
@ffi.Int()
@@ -2944,7 +3060,9 @@ final class __darwin_ucontext extends ffi.Struct {
external ffi.Pointer<__darwin_mcontext64> uc_mcontext;
}
typedef __darwin_sigset_t = __uint32_t;
typedef ucontext_t = __darwin_ucontext;
typedef sigset_t = __darwin_sigset_t;
typedef uid_t = __darwin_uid_t;
final class sigval extends ffi.Union {
@ffi.Int()
@@ -2968,9 +3086,6 @@ final class sigevent extends ffi.Struct {
external ffi.Pointer<pthread_attr_t> sigev_notify_attributes;
}
typedef pthread_attr_t = __darwin_pthread_attr_t;
typedef __darwin_pthread_attr_t = _opaque_pthread_attr_t;
final class __siginfo extends ffi.Struct {
@ffi.Int()
external int si_signo;
@@ -3001,12 +3116,7 @@ final class __siginfo extends ffi.Struct {
external ffi.Array<ffi.UnsignedLong> __pad;
}
typedef pid_t = __darwin_pid_t;
typedef __darwin_pid_t = __int32_t;
typedef __int32_t = ffi.Int;
typedef Dart__int32_t = int;
typedef uid_t = __darwin_uid_t;
typedef __darwin_uid_t = __uint32_t;
typedef siginfo_t = __siginfo;
final class __sigaction_u extends ffi.Union {
external ffi.Pointer<ffi.NativeFunction<ffi.Void Function(ffi.Int)>>
@@ -3020,7 +3130,7 @@ final class __sigaction_u extends ffi.Union {
}
final class __sigaction extends ffi.Struct {
external __sigaction_u __sigaction_u1;
external __sigaction_u __sigaction_u$1;
external ffi.Pointer<
ffi.NativeFunction<
@@ -3034,11 +3144,8 @@ final class __sigaction extends ffi.Struct {
external int sa_flags;
}
typedef siginfo_t = __siginfo;
typedef sigset_t = __darwin_sigset_t;
final class sigaction extends ffi.Struct {
external __sigaction_u __sigaction_u1;
external __sigaction_u __sigaction_u$1;
@sigset_t()
external int sa_mask;
@@ -3047,6 +3154,10 @@ final class sigaction extends ffi.Struct {
external int sa_flags;
}
typedef sig_tFunction = ffi.Void Function(ffi.Int);
typedef Dartsig_tFunction = void Function(int);
typedef sig_t = ffi.Pointer<ffi.NativeFunction<sig_tFunction>>;
final class sigvec extends ffi.Struct {
external ffi.Pointer<ffi.NativeFunction<ffi.Void Function(ffi.Int)>>
sv_handler;
@@ -3065,6 +3176,43 @@ final class sigstack extends ffi.Struct {
external int ss_onstack;
}
typedef int_least8_t = ffi.Int8;
typedef Dartint_least8_t = int;
typedef int_least16_t = ffi.Int16;
typedef Dartint_least16_t = int;
typedef int_least32_t = ffi.Int32;
typedef Dartint_least32_t = int;
typedef int_least64_t = ffi.Int64;
typedef Dartint_least64_t = int;
typedef uint_least8_t = ffi.Uint8;
typedef Dartuint_least8_t = int;
typedef uint_least16_t = ffi.Uint16;
typedef Dartuint_least16_t = int;
typedef uint_least32_t = ffi.Uint32;
typedef Dartuint_least32_t = int;
typedef uint_least64_t = ffi.Uint64;
typedef Dartuint_least64_t = int;
typedef int_fast8_t = ffi.Int8;
typedef Dartint_fast8_t = int;
typedef int_fast16_t = ffi.Int16;
typedef Dartint_fast16_t = int;
typedef int_fast32_t = ffi.Int32;
typedef Dartint_fast32_t = int;
typedef int_fast64_t = ffi.Int64;
typedef Dartint_fast64_t = int;
typedef uint_fast8_t = ffi.Uint8;
typedef Dartuint_fast8_t = int;
typedef uint_fast16_t = ffi.Uint16;
typedef Dartuint_fast16_t = int;
typedef uint_fast32_t = ffi.Uint32;
typedef Dartuint_fast32_t = int;
typedef uint_fast64_t = ffi.Uint64;
typedef Dartuint_fast64_t = int;
typedef intmax_t = ffi.Long;
typedef Dartintmax_t = int;
typedef uintmax_t = ffi.UnsignedLong;
typedef Dartuintmax_t = int;
final class timeval extends ffi.Struct {
@__darwin_time_t()
external int tv_sec;
@@ -3073,9 +3221,7 @@ final class timeval extends ffi.Struct {
external int tv_usec;
}
typedef __darwin_time_t = ffi.Long;
typedef Dart__darwin_time_t = int;
typedef __darwin_suseconds_t = __int32_t;
typedef rlim_t = __uint64_t;
final class rusage extends ffi.Struct {
external timeval ru_utime;
@@ -3125,6 +3271,8 @@ final class rusage extends ffi.Struct {
external int ru_nivcsw;
}
typedef rusage_info_t = ffi.Pointer<ffi.Void>;
final class rusage_info_v0 extends ffi.Struct {
@ffi.Array.multi([16])
external ffi.Array<ffi.Uint8> ri_uuid;
@@ -3730,6 +3878,8 @@ final class rusage_info_v6 extends ffi.Struct {
external ffi.Array<ffi.Uint64> ri_reserved;
}
typedef rusage_info_current = rusage_info_v6;
final class rlimit extends ffi.Struct {
@rlim_t()
external int rlim_cur;
@@ -3738,8 +3888,6 @@ final class rlimit extends ffi.Struct {
external int rlim_max;
}
typedef rlim_t = __uint64_t;
final class proc_rlimit_control_wakeupmon extends ffi.Struct {
@ffi.Uint32()
external int wm_flags;
@@ -3748,11 +3896,11 @@ final class proc_rlimit_control_wakeupmon extends ffi.Struct {
external int wm_rate;
}
typedef id_t = __darwin_id_t;
typedef __darwin_id_t = __uint32_t;
final class wait extends ffi.Opaque {}
typedef ct_rune_t = __darwin_ct_rune_t;
typedef rune_t = __darwin_rune_t;
final class div_t extends ffi.Struct {
@ffi.Int()
external int quot;
@@ -3784,18 +3932,18 @@ final class _malloc_zone_t extends ffi.Opaque {}
typedef malloc_zone_t = _malloc_zone_t;
typedef dev_t = __darwin_dev_t;
typedef __darwin_dev_t = __int32_t;
typedef mode_t = __darwin_mode_t;
typedef __darwin_mode_t = __uint16_t;
typedef __uint16_t = ffi.UnsignedShort;
typedef Dart__uint16_t = int;
typedef protect_func = ffi.Pointer<ffi.NativeFunction<protect_funcFunction>>;
typedef release_object_funcFunction = ffi.Void Function(
ffi.Pointer<ffi.Void> obj);
typedef Dartrelease_object_funcFunction = void Function(
ffi.Pointer<ffi.Void> obj);
typedef release_object_func
= ffi.Pointer<ffi.NativeFunction<release_object_funcFunction>>;
typedef protect_funcFunction = ffi.Void Function(
ffi.Pointer<ffi.Void> tun_interface, ffi.Int fd);
typedef Dartprotect_funcFunction = void Function(
ffi.Pointer<ffi.Void> tun_interface, int fd);
typedef resolve_process_func
= ffi.Pointer<ffi.NativeFunction<resolve_process_funcFunction>>;
typedef protect_func = ffi.Pointer<ffi.NativeFunction<protect_funcFunction>>;
typedef resolve_process_funcFunction = ffi.Pointer<ffi.Char> Function(
ffi.Pointer<ffi.Void> tun_interface,
ffi.Int protocol,
@@ -3808,12 +3956,35 @@ typedef Dartresolve_process_funcFunction = ffi.Pointer<ffi.Char> Function(
ffi.Pointer<ffi.Char> source,
ffi.Pointer<ffi.Char> target,
int uid);
typedef release_object_func
= ffi.Pointer<ffi.NativeFunction<release_object_funcFunction>>;
typedef release_object_funcFunction = ffi.Void Function(
ffi.Pointer<ffi.Void> obj);
typedef Dartrelease_object_funcFunction = void Function(
ffi.Pointer<ffi.Void> obj);
typedef resolve_process_func
= ffi.Pointer<ffi.NativeFunction<resolve_process_funcFunction>>;
typedef GoInt8 = ffi.SignedChar;
typedef DartGoInt8 = int;
typedef GoUint8 = ffi.UnsignedChar;
typedef DartGoUint8 = int;
typedef GoInt16 = ffi.Short;
typedef DartGoInt16 = int;
typedef GoUint16 = ffi.UnsignedShort;
typedef DartGoUint16 = int;
typedef GoInt32 = ffi.Int;
typedef DartGoInt32 = int;
typedef GoUint32 = ffi.UnsignedInt;
typedef DartGoUint32 = int;
typedef GoInt64 = ffi.LongLong;
typedef DartGoInt64 = int;
typedef GoUint64 = ffi.UnsignedLongLong;
typedef DartGoUint64 = int;
typedef GoInt = GoInt64;
typedef GoUint = GoUint64;
typedef GoUintptr = ffi.Size;
typedef DartGoUintptr = int;
typedef GoFloat32 = ffi.Float;
typedef DartGoFloat32 = double;
typedef GoFloat64 = ffi.Double;
typedef DartGoFloat64 = double;
typedef GoString = _GoString_;
typedef GoMap = ffi.Pointer<ffi.Void>;
typedef GoChan = ffi.Pointer<ffi.Void>;
final class GoInterface extends ffi.Struct {
external ffi.Pointer<ffi.Void> t;
@@ -3831,12 +4002,6 @@ final class GoSlice extends ffi.Struct {
external int cap;
}
typedef GoInt = GoInt64;
typedef GoInt64 = ffi.LongLong;
typedef DartGoInt64 = int;
typedef GoUint8 = ffi.UnsignedChar;
typedef DartGoUint8 = int;
const int __has_safe_buffers = 1;
const int __DARWIN_ONLY_64_BIT_INO_T = 1;

View File

@@ -1,5 +1,6 @@
import 'dart:async';
import 'dart:convert';
import 'dart:isolate';
import 'package:fl_clash/clash/message.dart';
import 'package:fl_clash/common/common.dart';
@@ -19,11 +20,15 @@ mixin ClashInterface {
FutureOr<String> validateConfig(String data);
FutureOr<Result> getConfig(String path);
Future<String> asyncTestDelay(String url, String proxyName);
FutureOr<String> updateConfig(UpdateConfigParams updateConfigParams);
FutureOr<String> updateConfig(UpdateParams updateParams);
FutureOr<String> getProxies();
FutureOr<String> setupConfig(SetupParams setupParams);
FutureOr<Map> getProxies();
FutureOr<String> changeProxy(ChangeProxyParams changeProxyParams);
@@ -66,18 +71,12 @@ mixin ClashInterface {
FutureOr<bool> closeConnections();
FutureOr<String> getProfile(String id);
FutureOr<bool> resetConnections();
Future<bool> setState(CoreState state);
}
mixin AndroidClashInterface {
Future<bool> setFdMap(int fd);
Future<bool> setProcessMap(ProcessMapItem item);
// Future<bool> stopTun();
Future<bool> updateDns(String value);
Future<AndroidVpnOptions?> getAndroidVpnOptions();
@@ -90,55 +89,23 @@ mixin AndroidClashInterface {
abstract class ClashHandlerInterface with ClashInterface {
Map<String, Completer> callbackCompleterMap = {};
Future<bool> nextHandleResult(ActionResult result, Completer? completer) =>
Future.value(false);
handleResult(ActionResult result) async {
final completer = callbackCompleterMap[result.id];
try {
switch (result.method) {
case ActionMethod.initClash:
case ActionMethod.shutdown:
case ActionMethod.getIsInit:
case ActionMethod.startListener:
case ActionMethod.resetTraffic:
case ActionMethod.closeConnections:
case ActionMethod.closeConnection:
case ActionMethod.stopListener:
case ActionMethod.setState:
case ActionMethod.crash:
completer?.complete(result.data as bool);
return;
case ActionMethod.changeProxy:
case ActionMethod.getProxies:
case ActionMethod.getTraffic:
case ActionMethod.getTotalTraffic:
case ActionMethod.asyncTestDelay:
case ActionMethod.getConnections:
case ActionMethod.getExternalProviders:
case ActionMethod.getExternalProvider:
case ActionMethod.validateConfig:
case ActionMethod.updateConfig:
case ActionMethod.updateGeoData:
case ActionMethod.updateExternalProvider:
case ActionMethod.sideLoadExternalProvider:
case ActionMethod.getCountryCode:
case ActionMethod.getMemory:
completer?.complete(result.data as String);
return;
case ActionMethod.message:
clashMessage.controller.add(result.data as String);
clashMessage.controller.add(result.data);
completer?.complete(true);
return;
case ActionMethod.getConfig:
completer?.complete(result.toResult);
return;
default:
final isHandled = await nextHandleResult(result, completer);
if (isHandled) {
return;
}
completer?.complete(result.data);
return;
}
} catch (_) {
commonPrint.log(result.id);
} catch (e) {
commonPrint.log("${result.id} error $e");
}
}
@@ -153,18 +120,21 @@ abstract class ClashHandlerInterface with ClashInterface {
dynamic data,
Duration? timeout,
FutureOr<T> Function()? onTimeout,
T? defaultValue,
}) async {
final id = "${method.name}#${utils.id}";
callbackCompleterMap[id] = Completer<T>();
dynamic defaultValue;
if (T == String) {
defaultValue = "";
}
if (T == bool) {
defaultValue = false;
dynamic mDefaultValue = defaultValue;
if (mDefaultValue == null) {
if (T == String) {
mDefaultValue = "";
} else if (T == bool) {
mDefaultValue = false;
} else if (T == Map) {
mDefaultValue = {};
}
}
sendMessage(
@@ -173,7 +143,6 @@ abstract class ClashHandlerInterface with ClashInterface {
id: id,
method: method,
data: data,
defaultValue: defaultValue,
),
),
);
@@ -185,7 +154,7 @@ abstract class ClashHandlerInterface with ClashInterface {
},
onTimeout: onTimeout ??
() {
return defaultValue;
return mDefaultValue;
},
functionName: id,
);
@@ -211,6 +180,7 @@ abstract class ClashHandlerInterface with ClashInterface {
shutdown() async {
return await invoke<bool>(
method: ActionMethod.shutdown,
timeout: Duration(seconds: 1),
);
}
@@ -237,14 +207,34 @@ abstract class ClashHandlerInterface with ClashInterface {
}
@override
Future<String> updateConfig(UpdateConfigParams updateConfigParams) async {
Future<String> updateConfig(UpdateParams updateParams) async {
return await invoke<String>(
method: ActionMethod.updateConfig,
data: json.encode(updateConfigParams),
data: json.encode(updateParams),
timeout: Duration(minutes: 2),
);
}
@override
Future<Result> getConfig(String path) async {
final res = await invoke<Result>(
method: ActionMethod.getConfig,
data: path,
timeout: Duration(minutes: 2),
defaultValue: Result.success({}),
);
return res;
}
@override
Future<String> setupConfig(SetupParams setupParams) async {
final data = await Isolate.run(() => json.encode(setupParams));
return await invoke<String>(
method: ActionMethod.setupConfig,
data: data,
timeout: Duration(minutes: 2),
);
}
@override
Future<bool> crash() {
@@ -254,8 +244,8 @@ abstract class ClashHandlerInterface with ClashInterface {
}
@override
Future<String> getProxies() {
return invoke<String>(
Future<Map> getProxies() {
return invoke<Map>(
method: ActionMethod.getProxies,
timeout: Duration(seconds: 5),
);
@@ -330,17 +320,16 @@ abstract class ClashHandlerInterface with ClashInterface {
}
@override
Future<bool> closeConnection(String id) {
Future<bool> resetConnections() {
return invoke<bool>(
method: ActionMethod.closeConnection,
data: id,
method: ActionMethod.resetConnections,
);
}
@override
Future<String> getProfile(String id) {
return invoke<String>(
method: ActionMethod.getProfile,
Future<bool> closeConnection(String id) {
return invoke<bool>(
method: ActionMethod.closeConnection,
data: id,
);
}

View File

@@ -6,7 +6,7 @@ import 'dart:isolate';
import 'dart:ui';
import 'package:ffi/ffi.dart';
import 'package:fl_clash/common/constant.dart';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/plugins/service.dart';
@@ -62,26 +62,6 @@ class ClashLib extends ClashHandlerInterface with AndroidClashInterface {
return _instance!;
}
@override
Future<bool> nextHandleResult(result, completer) async {
switch (result.method) {
case ActionMethod.setFdMap:
case ActionMethod.setProcessMap:
case ActionMethod.stopTun:
case ActionMethod.updateDns:
completer?.complete(result.data as bool);
return true;
case ActionMethod.getRunTime:
case ActionMethod.startTun:
case ActionMethod.getAndroidVpnOptions:
case ActionMethod.getCurrentProfileName:
completer?.complete(result.data as String);
return true;
default:
return false;
}
}
@override
destroy() async {
await service?.destroy();
@@ -106,22 +86,6 @@ class ClashLib extends ClashHandlerInterface with AndroidClashInterface {
sendPort?.send(message);
}
@override
Future<bool> setFdMap(int fd) {
return invoke<bool>(
method: ActionMethod.setFdMap,
data: json.encode(fd),
);
}
@override
Future<bool> setProcessMap(item) {
return invoke<bool>(
method: ActionMethod.setProcessMap,
data: item,
);
}
// @override
// Future<bool> stopTun() {
// return invoke<bool>(
@@ -267,9 +231,32 @@ class ClashLibHandler {
return true;
}
DateTime? getRunTime() {
final runTimeRaw = clashFFI.getRunTime();
final runTimeString = runTimeRaw.cast<Utf8>().toDartString();
if (runTimeString.isEmpty) {
return null;
}
return DateTime.fromMillisecondsSinceEpoch(int.parse(runTimeString));
}
Future<Map<String, dynamic>> getConfig(String id) async {
final path = await appPath.getProfilePath(id);
final pathChar = path.toNativeUtf8().cast<Char>();
final configRaw = clashFFI.getConfig(pathChar);
final configString = configRaw.cast<Utf8>().toDartString();
if (configString.isEmpty) {
return {};
}
final config = json.decode(configString);
malloc.free(pathChar);
clashFFI.freeCString(configRaw);
return config;
}
Future<String> quickStart(
InitParams initParams,
UpdateConfigParams updateConfigParams,
SetupParams setupParams,
CoreState state,
) {
final completer = Completer<String>();
@@ -280,7 +267,7 @@ class ClashLibHandler {
receiver.close();
}
});
final params = json.encode(updateConfigParams);
final params = json.encode(setupParams);
final initValue = json.encode(initParams);
final stateParams = json.encode(state);
final initParamsChar = initValue.toNativeUtf8().cast<Char>();
@@ -297,15 +284,10 @@ class ClashLibHandler {
malloc.free(stateParamsChar);
return completer.future;
}
DateTime? getRunTime() {
final runTimeRaw = clashFFI.getRunTime();
final runTimeString = runTimeRaw.cast<Utf8>().toDartString();
clashFFI.freeCString(runTimeRaw);
if (runTimeString.isEmpty) return null;
return DateTime.fromMillisecondsSinceEpoch(int.parse(runTimeString));
}
}
ClashLib? get clashLib =>
Platform.isAndroid && !globalState.isService ? ClashLib() : null;
ClashLibHandler? get clashLibHandler =>
Platform.isAndroid && globalState.isService ? ClashLibHandler() : null;

View File

@@ -1,20 +1,19 @@
import 'dart:async';
import 'dart:convert';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart';
import 'package:flutter/foundation.dart';
class ClashMessage {
final controller = StreamController<String>();
final controller = StreamController<Map<String, Object?>>();
ClashMessage._() {
controller.stream.listen(
(message) {
if(message.isEmpty){
if (message.isEmpty) {
return;
}
final m = AppMessage.fromJson(json.decode(message));
final m = AppMessage.fromJson(message);
for (final AppMessageListener listener in _listeners) {
switch (m.type) {
case AppMessageType.log:

View File

@@ -1,7 +1,6 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:fl_clash/clash/interface.dart';
import 'package:fl_clash/common/common.dart';
@@ -51,29 +50,24 @@ class ClashService extends ClashHandlerInterface {
await _destroySocket();
socketCompleter.complete(socket);
socket
.transform(
StreamTransformer<Uint8List, String>.fromHandlers(
handleData: (Uint8List data, EventSink<String> sink) {
sink.add(utf8.decode(data, allowMalformed: true));
},
),
)
.transform(uint8ListToListIntConverter)
.transform(utf8.decoder)
.transform(LineSplitter())
.listen(
(data) {
handleResult(
ActionResult.fromJson(
json.decode(data.trim()),
),
);
},
(data) {
handleResult(
ActionResult.fromJson(
json.decode(data.trim()),
),
);
},
);
}
}, (error, stack) {
commonPrint.log(error.toString());
if(error is SocketException){
if (error is SocketException) {
globalState.showNotifier(error.toString());
globalState.appController.restartCore();
// globalState.appController.restartCore();
}
});
}
@@ -92,12 +86,11 @@ class ClashService extends ClashHandlerInterface {
final arg = Platform.isWindows
? "${serverSocket.port}"
: serverSocket.address.address;
bool isSuccess = false;
if (Platform.isWindows && await system.checkIsAdmin()) {
isSuccess = await request.startCoreByHelper(arg);
}
if (isSuccess) {
return;
final isSuccess = await request.startCoreByHelper(arg);
if (isSuccess) {
return;
}
}
process = await Process.start(
appPath.corePath,
@@ -105,7 +98,13 @@ class ClashService extends ClashHandlerInterface {
arg,
],
);
process!.stdout.listen((_) {});
process?.stdout.listen((_) {});
process?.stderr.listen((e) {
final error = utf8.decode(e);
if (error.isNotEmpty) {
commonPrint.log(error);
}
});
isStarting = false;
}

View File

@@ -3,7 +3,9 @@ export 'app_localizations.dart';
export 'color.dart';
export 'constant.dart';
export 'context.dart';
export 'converter.dart';
export 'datetime.dart';
export 'fixed.dart';
export 'function.dart';
export 'future.dart';
export 'http.dart';
@@ -12,28 +14,27 @@ export 'iterable.dart';
export 'keyboard.dart';
export 'launch.dart';
export 'link.dart';
export 'list.dart';
export 'lock.dart';
export 'measure.dart';
export 'mixin.dart';
export 'navigation.dart';
export 'navigator.dart';
export 'network.dart';
export 'num.dart';
export 'utils.dart';
export 'package.dart';
export 'path.dart';
export 'picker.dart';
export 'preferences.dart';
export 'print.dart';
export 'protocol.dart';
export 'proxy.dart';
export 'render.dart';
export 'request.dart';
export 'scroll.dart';
export 'string.dart';
export 'system.dart';
export 'text.dart';
export 'tray.dart';
export 'utils.dart';
export 'window.dart';
export 'windows.dart';
export 'render.dart';
export 'mixin.dart';
export 'print.dart';

View File

@@ -16,16 +16,19 @@ const browserUa =
const packageName = "com.follow.clash";
final unixSocketPath = "/tmp/FlClashSocket_${Random().nextInt(10000)}.sock";
const helperPort = 47890;
const helperTag = "2024125";
const maxTextScale = 1.4;
const minTextScale = 0.8;
final baseInfoEdgeInsets = EdgeInsets.symmetric(
vertical: 16.ap,
horizontal: 16.ap,
);
final defaultTextScaleFactor = WidgetsBinding.instance.platformDispatcher.textScaleFactor;
final defaultTextScaleFactor =
WidgetsBinding.instance.platformDispatcher.textScaleFactor;
const httpTimeoutDuration = Duration(milliseconds: 5000);
const moreDuration = Duration(milliseconds: 100);
const animateDuration = Duration(milliseconds: 100);
const midDuration = Duration(milliseconds: 200);
const commonDuration = Duration(milliseconds: 300);
const defaultUpdateDuration = Duration(days: 1);
const mmdbFileName = "geoip.metadb";
@@ -75,22 +78,33 @@ const viewModeColumnsMap = {
ViewMode.desktop: [4, 3],
};
const defaultPrimaryColor = 0xFF795548;
// const proxiesStoreKey = PageStorageKey<String>('proxies');
// const toolsStoreKey = PageStorageKey<String>('tools');
// const profilesStoreKey = PageStorageKey<String>('profiles');
const defaultPrimaryColor = 0XFFD8C0C3;
double getWidgetHeight(num lines) {
return max(lines * 84 + (lines - 1) * 16, 0).ap;
}
const maxLength = 150;
final mainIsolate = "FlClashMainIsolate";
final serviceIsolate = "FlClashServiceIsolate";
const defaultPrimaryColors = [
defaultPrimaryColor,
0xFF795548,
0xFF03A9F4,
0xFFFFFF00,
0XFFBBC9CC,
0XFFABD397,
0XFFD8C0C3,
defaultPrimaryColor,
0XFF665390,
];
const scriptTemplate = """
const main = (config) => {
return config;
}""";

32
lib/common/converter.dart Normal file
View File

@@ -0,0 +1,32 @@
import 'dart:convert';
import 'dart:typed_data';
class Uint8ListToListIntConverter extends Converter<Uint8List, List<int>> {
@override
List<int> convert(Uint8List input) {
return input.toList();
}
@override
Sink<Uint8List> startChunkedConversion(Sink<List<int>> sink) {
return _Uint8ListToListIntConverterSink(sink);
}
}
class _Uint8ListToListIntConverterSink implements Sink<Uint8List> {
const _Uint8ListToListIntConverterSink(this._target);
final Sink<List<int>> _target;
@override
void add(Uint8List data) {
_target.add(data.toList());
}
@override
void close() {
_target.close();
}
}
final uint8ListToListIntConverter = Uint8ListToListIntConverter();

79
lib/common/fixed.dart Normal file
View File

@@ -0,0 +1,79 @@
import 'iterable.dart';
class FixedList<T> {
final int maxLength;
final List<T> _list;
FixedList(this.maxLength, {List<T>? list})
: _list = (list ?? [])..truncate(maxLength);
add(T item) {
_list.add(item);
_list.truncate(maxLength);
}
clear() {
_list.clear();
}
List<T> get list => List.unmodifiable(_list);
int get length => _list.length;
T operator [](int index) => _list[index];
FixedList<T> copyWith() {
return FixedList(
maxLength,
list: _list,
);
}
}
class FixedMap<K, V> {
int maxLength;
late Map<K, V> _map;
FixedMap(this.maxLength, {Map<K, V>? map}) {
_map = map ?? {};
}
updateCacheValue(K key, V Function() callback) {
final realValue = _map.updateCacheValue(
key,
callback,
);
_adjustMap();
return realValue;
}
clear() {
_map.clear();
}
updateMaxLength(int size) {
maxLength = size;
_adjustMap();
}
updateMap(Map<K, V> map) {
_map = map;
_adjustMap();
}
_adjustMap() {
if (_map.length > maxLength) {
_map = Map.fromEntries(
map.entries.toList()..truncate(maxLength),
);
}
}
V? get(K key) => _map[key];
bool containsKey(K key) => _map.containsKey(key);
int get length => _map.length;
Map<K, V> get map => Map.unmodifiable(_map);
}

View File

@@ -1,10 +1,12 @@
import 'dart:async';
import 'package:fl_clash/enum/enum.dart';
class Debouncer {
final Map<dynamic, Timer?> _operations = {};
final Map<FunctionTag, Timer?> _operations = {};
call(
dynamic tag,
FunctionTag tag,
Function func, {
List<dynamic>? args,
Duration duration = const Duration(milliseconds: 600),
@@ -33,10 +35,10 @@ class Debouncer {
}
class Throttler {
final Map<dynamic, Timer?> _operations = {};
final Map<FunctionTag, Timer?> _operations = {};
call(
dynamic tag,
FunctionTag tag,
Function func, {
List<dynamic>? args,
Duration duration = const Duration(milliseconds: 600),

View File

@@ -18,6 +18,7 @@ class FlClashHttpOverrides extends HttpOverrides {
@override
HttpClient createHttpClient(SecurityContext? context) {
final client = super.createHttpClient(context);
client.badCertificateCallback = (_, __, ___) => true;
client.findProxy = handleFindProxy;
return client;
}

View File

@@ -38,6 +38,43 @@ extension IterableExt<T> on Iterable<T> {
count++;
}
}
Iterable<T> takeLast({int count = 50}) {
if (count <= 0) return Iterable.empty();
return count >= length ? this : toList().skip(length - count);
}
}
extension ListExt<T> on List<T> {
void truncate(int maxLength) {
assert(maxLength > 0);
if (length > maxLength) {
removeRange(0, length - maxLength);
}
}
List<T> intersection(List<T> list) {
return where((item) => list.contains(item)).toList();
}
List<List<T>> batch(int maxConcurrent) {
final batches = (length / maxConcurrent).ceil();
final List<List<T>> res = [];
for (int i = 0; i < batches; i++) {
if (i != batches - 1) {
res.add(sublist(i * maxConcurrent, maxConcurrent * (i + 1)));
} else {
res.add(sublist(i * maxConcurrent, length));
}
}
return res;
}
List<T> safeSublist(int start) {
if (start <= 0) return this;
if (start > length) return [];
return sublist(start);
}
}
extension DoubleListExt on List<double> {
@@ -67,9 +104,9 @@ extension DoubleListExt on List<double> {
}
extension MapExt<K, V> on Map<K, V> {
getCacheValue(K key, V defaultValue) {
updateCacheValue(K key, V Function() callback) {
if (this[key] == null) {
this[key] = defaultValue;
this[key] = callback();
}
return this[key];
}

View File

@@ -1,93 +0,0 @@
import 'dart:collection';
class FixedList<T> {
final int maxLength;
final List<T> _list;
FixedList(this.maxLength, {List<T>? list}) : _list = list ?? [];
add(T item) {
if (_list.length == maxLength) {
_list.removeAt(0);
}
_list.add(item);
}
clear() {
_list.clear();
}
List<T> get list => List.unmodifiable(_list);
int get length => _list.length;
T operator [](int index) => _list[index];
FixedList<T> copyWith() {
return FixedList(
maxLength,
list: _list,
);
}
}
class FixedMap<K, V> {
int maxSize;
final Map<K, V> _map = {};
final Queue<K> _queue = Queue<K>();
FixedMap(this.maxSize);
put(K key, V value) {
if (_map.length == maxSize) {
final oldestKey = _queue.removeFirst();
_map.remove(oldestKey);
}
_map[key] = value;
_queue.add(key);
return value;
}
clear() {
_map.clear();
_queue.clear();
}
updateMaxSize(int size){
maxSize = size;
}
V? get(K key) => _map[key];
bool containsKey(K key) => _map.containsKey(key);
int get length => _map.length;
Map<K, V> get map => Map.unmodifiable(_map);
}
extension ListExtension<T> on List<T> {
List<T> intersection(List<T> list) {
return where((item) => list.contains(item)).toList();
}
List<List<T>> batch(int maxConcurrent) {
final batches = (length / maxConcurrent).ceil();
final List<List<T>> res = [];
for (int i = 0; i < batches; i++) {
if (i != batches - 1) {
res.add(sublist(i * maxConcurrent, maxConcurrent * (i + 1)));
} else {
res.add(sublist(i * maxConcurrent, length));
}
}
return res;
}
List<T> safeSublist(int start) {
if (start <= 0) return this;
if (start > length) return [];
return sublist(start);
}
}

View File

@@ -32,9 +32,9 @@ class Measure {
}
double get bodyMediumHeight {
return _measureMap.getCacheValue(
return _measureMap.updateCacheValue(
"bodyMediumHeight",
computeTextSize(
() => computeTextSize(
Text(
"X",
style: context.textTheme.bodyMedium,
@@ -43,10 +43,22 @@ class Measure {
);
}
double get bodyLargeHeight {
return _measureMap.updateCacheValue(
"bodyLargeHeight",
() => computeTextSize(
Text(
"X",
style: context.textTheme.bodyLarge,
),
).height,
);
}
double get bodySmallHeight {
return _measureMap.getCacheValue(
return _measureMap.updateCacheValue(
"bodySmallHeight",
computeTextSize(
() => computeTextSize(
Text(
"X",
style: context.textTheme.bodySmall,
@@ -56,9 +68,9 @@ class Measure {
}
double get labelSmallHeight {
return _measureMap.getCacheValue(
return _measureMap.updateCacheValue(
"labelSmallHeight",
computeTextSize(
() => computeTextSize(
Text(
"X",
style: context.textTheme.labelSmall,
@@ -68,9 +80,9 @@ class Measure {
}
double get labelMediumHeight {
return _measureMap.getCacheValue(
return _measureMap.updateCacheValue(
"labelMediumHeight",
computeTextSize(
() => computeTextSize(
Text(
"X",
style: context.textTheme.labelMedium,
@@ -80,9 +92,9 @@ class Measure {
}
double get titleLargeHeight {
return _measureMap.getCacheValue(
return _measureMap.updateCacheValue(
"titleLargeHeight",
computeTextSize(
() => computeTextSize(
Text(
"X",
style: context.textTheme.titleLarge,
@@ -92,9 +104,9 @@ class Measure {
}
double get titleMediumHeight {
return _measureMap.getCacheValue(
return _measureMap.updateCacheValue(
"titleMediumHeight",
computeTextSize(
() => computeTextSize(
Text(
"X",
style: context.textTheme.titleMedium,

View File

@@ -1,6 +1,6 @@
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/fragments/fragments.dart';
import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/views/views.dart';
import 'package:flutter/material.dart';
class Navigation {
@@ -12,17 +12,17 @@ class Navigation {
}) {
return [
const NavigationItem(
keep: false,
icon: Icon(Icons.space_dashboard),
label: PageLabel.dashboard,
keep: false,
fragment: DashboardFragment(
view: DashboardView(
key: GlobalObjectKey(PageLabel.dashboard),
),
),
NavigationItem(
icon: const Icon(Icons.article),
label: PageLabel.proxies,
fragment: const ProxiesFragment(
view: const ProxiesView(
key: GlobalObjectKey(
PageLabel.proxies,
),
@@ -34,7 +34,7 @@ class Navigation {
const NavigationItem(
icon: Icon(Icons.folder),
label: PageLabel.profiles,
fragment: ProfilesFragment(
view: ProfilesView(
key: GlobalObjectKey(
PageLabel.profiles,
),
@@ -43,7 +43,7 @@ class Navigation {
const NavigationItem(
icon: Icon(Icons.view_timeline),
label: PageLabel.requests,
fragment: RequestsFragment(
view: RequestsView(
key: GlobalObjectKey(
PageLabel.requests,
),
@@ -54,7 +54,7 @@ class Navigation {
const NavigationItem(
icon: Icon(Icons.ballot),
label: PageLabel.connections,
fragment: ConnectionsFragment(
view: ConnectionsView(
key: GlobalObjectKey(
PageLabel.connections,
),
@@ -66,8 +66,7 @@ class Navigation {
icon: Icon(Icons.storage),
label: PageLabel.resources,
description: "resourcesDesc",
keep: false,
fragment: Resources(
view: ResourcesView(
key: GlobalObjectKey(
PageLabel.resources,
),
@@ -77,7 +76,7 @@ class Navigation {
NavigationItem(
icon: const Icon(Icons.adb),
label: PageLabel.logs,
fragment: const LogsFragment(
view: const LogsView(
key: GlobalObjectKey(
PageLabel.logs,
),
@@ -90,7 +89,7 @@ class Navigation {
const NavigationItem(
icon: Icon(Icons.construction),
label: PageLabel.tools,
fragment: ToolsFragment(
view: ToolsView(
key: GlobalObjectKey(
PageLabel.tools,
),

View File

@@ -2,6 +2,7 @@ import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/dialog.dart';
import 'package:flutter/material.dart';
class BaseNavigator {
@@ -19,6 +20,21 @@ class BaseNavigator {
),
);
}
static Future<T?> modal<T>(BuildContext context, Widget child) async {
if (globalState.appState.viewMode != ViewMode.mobile) {
return await globalState.showCommonDialog<T>(
child: CommonModal(
child: child,
),
);
}
return await Navigator.of(context).push<T>(
CommonRoute(
builder: (context) => child,
),
);
}
}
class CommonDesktopRoute<T> extends PageRoute<T> {
@@ -218,8 +234,7 @@ class _CommonPageTransitionState extends State<CommonPageTransition> {
begin: const _CommonEdgeShadowDecoration(),
end: _CommonEdgeShadowDecoration(
<Color>[
widget.context.colorScheme.inverseSurface
.withValues(alpha: 0.02),
widget.context.colorScheme.inverseSurface.withValues(alpha: 0.02),
Colors.transparent,
],
),

View File

@@ -1,11 +1,10 @@
import 'dart:async';
import 'dart:io';
import 'package:fl_clash/common/common.dart';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
import 'constant.dart';
class AppPath {
static AppPath? _instance;
Completer<Directory> dataDir = Completer();
@@ -73,16 +72,33 @@ class AppPath {
return join(directory.path, profilesDirectoryName);
}
Future<String?> getProfilePath(String? id) async {
if (id == null) return null;
Future<String> getProfilePath(String id) async {
final directory = await profilesPath;
return join(directory, "$id.yaml");
}
Future<String?> getProvidersPath(String? id) async {
if (id == null) return null;
Future<String> getProvidersDirPath(String id) async {
final directory = await profilesPath;
return join(directory, "providers", id);
return join(
directory,
"providers",
id,
);
}
Future<String> getProvidersFilePath(
String id,
String type,
String url,
) async {
final directory = await profilesPath;
return join(
directory,
"providers",
id,
type,
url.toMd5(),
);
}
Future<String> get tempPath async {

View File

@@ -15,7 +15,7 @@ class CommonPrint {
log(String? text) {
final payload = "[FlClash] $text";
debugPrint(payload);
if (globalState.isService) {
if (!globalState.isInit) {
return;
}
globalState.appController.addLog(

View File

@@ -23,14 +23,14 @@ class Render {
pause() {
throttler.call(
DebounceTag.renderPause,
FunctionTag.renderPause,
_pause,
duration: Duration(seconds: 5),
);
}
resume() {
throttler.cancel(DebounceTag.renderPause);
throttler.cancel(FunctionTag.renderPause);
_resume();
}

View File

@@ -43,6 +43,16 @@ class Request {
return response;
}
Future<Response> getTextResponseForUrl(String url) async {
final response = await _clashDio.get(
url,
options: Options(
responseType: ResponseType.plain,
),
);
return response;
}
Future<MemoryImage?> getImage(String url) async {
if (url.isEmpty) return null;
final response = await _dio.get<Uint8List>(
@@ -130,7 +140,7 @@ class Request {
if (response.statusCode != HttpStatus.ok) {
return false;
}
return (response.data as String) == helperTag;
return (response.data as String) == globalState.coreSHA256;
} catch (_) {
return false;
}

View File

@@ -1,6 +1,8 @@
import 'dart:convert';
import 'dart:typed_data';
import 'package:crypto/crypto.dart';
import 'print.dart';
extension StringExtension on String {
@@ -8,6 +10,13 @@ extension StringExtension on String {
return RegExp(r'^(http|https|ftp)://').hasMatch(this);
}
dynamic get splitByMultipleSeparators {
final parts =
split(RegExp(r'[, ;]+')).where((part) => part.isNotEmpty).toList();
return parts.length > 1 ? parts : this;
}
int compareToLower(String other) {
return toLowerCase().compareTo(
other.toLowerCase(),
@@ -38,6 +47,10 @@ extension StringExtension on String {
}
}
bool get isSvg {
return endsWith(".svg");
}
bool get isRegex {
try {
RegExp(this);
@@ -47,6 +60,15 @@ extension StringExtension on String {
return false;
}
}
String toMd5() {
final bytes = utf8.encode(this);
return md5.convert(bytes).toString();
}
// bool containsToLower(String target) {
// return toLowerCase().contains(target);
// }
}
extension StringExtensionSafe on String? {

View File

@@ -10,6 +10,7 @@ import 'package:flutter/services.dart';
class System {
static System? _instance;
List<String>? originDns;
System._internal();
@@ -55,18 +56,24 @@ class System {
}
Future<AuthorizeCode> authorizeCore() async {
if (Platform.isAndroid) {
return AuthorizeCode.error;
}
final corePath = appPath.corePath.replaceAll(' ', '\\\\ ');
final isAdmin = await checkIsAdmin();
if (isAdmin) {
return AuthorizeCode.none;
}
if (Platform.isWindows) {
final result = await windows?.registerService();
if (result == true) {
return AuthorizeCode.success;
}
return AuthorizeCode.error;
} else if (Platform.isMacOS) {
}
if (Platform.isMacOS) {
final shell = 'chown root:admin $corePath; chmod +sx $corePath';
final arguments = [
"-e",
@@ -98,6 +105,100 @@ class System {
return AuthorizeCode.error;
}
Future<String?> getMacOSDefaultServiceName() async {
if (!Platform.isMacOS) {
return null;
}
final result = await Process.run('route', ['-n', 'get', 'default']);
final output = result.stdout.toString();
final deviceLine = output
.split('\n')
.firstWhere((s) => s.contains('interface:'), orElse: () => "");
final lineSplits = deviceLine.trim().split(' ');
if (lineSplits.length != 2) {
return null;
}
final device = lineSplits[1];
final serviceResult = await Process.run(
'networksetup',
['-listnetworkserviceorder'],
);
final serviceResultOutput = serviceResult.stdout.toString();
final currentService = serviceResultOutput.split('\n\n').firstWhere(
(s) => s.contains("Device: $device"),
orElse: () => "",
);
if (currentService.isEmpty) {
return null;
}
final currentServiceNameLine = currentService.split("\n").firstWhere(
(line) => RegExp(r'^\(\d+\).*').hasMatch(line),
orElse: () => "");
final currentServiceNameLineSplits =
currentServiceNameLine.trim().split(' ');
if (currentServiceNameLineSplits.length < 2) {
return null;
}
return currentServiceNameLineSplits[1];
}
Future<List<String>?> getMacOSOriginDns() async {
if (!Platform.isMacOS) {
return null;
}
final deviceServiceName = await getMacOSDefaultServiceName();
if (deviceServiceName == null) {
return null;
}
final result = await Process.run(
'networksetup',
['-getdnsservers', deviceServiceName],
);
final output = result.stdout.toString().trim();
if (output.startsWith("There aren't any DNS Servers set on")) {
originDns = [];
} else {
originDns = output.split("\n");
}
return originDns;
}
setMacOSDns(bool restore) async {
if (!Platform.isMacOS) {
return;
}
final serviceName = await getMacOSDefaultServiceName();
if (serviceName == null) {
return;
}
List<String>? nextDns;
if (restore) {
nextDns = originDns;
} else {
final originDns = await system.getMacOSOriginDns();
if (originDns == null) {
return;
}
final needAddDns = "223.5.5.5";
if (originDns.contains(needAddDns)) {
return;
}
nextDns = List.from(originDns)..add(needAddDns);
}
if (nextDns == null) {
return;
}
await Process.run(
'networksetup',
[
'-setdnsservers',
serviceName,
if (nextDns.isNotEmpty) ...nextDns,
if (nextDns.isEmpty) "Empty",
],
);
}
back() async {
await app?.moveTaskToBack();
await window?.hide();

View File

@@ -12,32 +12,35 @@ class CommonTheme {
) : _colorMap = {};
Color get darkenSecondaryContainer {
return _colorMap.getCacheValue(
return _colorMap.updateCacheValue(
"darkenSecondaryContainer",
context.colorScheme.secondaryContainer.blendDarken(context, factor: 0.1),
() => context.colorScheme.secondaryContainer
.blendDarken(context, factor: 0.1),
);
}
Color get darkenSecondaryContainerLighter {
return _colorMap.getCacheValue(
return _colorMap.updateCacheValue(
"darkenSecondaryContainerLighter",
context.colorScheme.secondaryContainer
() => context.colorScheme.secondaryContainer
.blendDarken(context, factor: 0.1)
.opacity60,
);
}
Color get darken2SecondaryContainer {
return _colorMap.getCacheValue(
return _colorMap.updateCacheValue(
"darken2SecondaryContainer",
context.colorScheme.secondaryContainer.blendDarken(context, factor: 0.2),
() => context.colorScheme.secondaryContainer
.blendDarken(context, factor: 0.2),
);
}
Color get darken3PrimaryContainer {
return _colorMap.getCacheValue(
return _colorMap.updateCacheValue(
"darken3PrimaryContainer",
context.colorScheme.primaryContainer.blendDarken(context, factor: 0.3),
() => context.colorScheme.primaryContainer
.blendDarken(context, factor: 0.3),
);
}
}

View File

@@ -1,10 +1,13 @@
import 'dart:async';
import 'dart:io';
import 'dart:math';
import 'dart:ui';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:lpinyin/lpinyin.dart';
class Utils {
@@ -230,7 +233,7 @@ class Utils {
}
int getProfilesColumns(double viewWidth) {
return max((viewWidth / 350).floor(), 1);
return max((viewWidth / 320).floor(), 1);
}
final _indexPrimary = [
@@ -323,6 +326,72 @@ class Utils {
}
return "";
}
SingleActivator controlSingleActivator(LogicalKeyboardKey trigger) {
final control = Platform.isMacOS ? false : true;
return SingleActivator(
trigger,
control: control,
meta: !control,
);
}
// dynamic convertYamlNode(dynamic node) {
// if (node is YamlMap) {
// final map = <String, dynamic>{};
// YamlNode? mergeKeyNode;
// for (final entry in node.nodes.entries) {
// if (entry.key is YamlScalar &&
// (entry.key as YamlScalar).value == '<<') {
// mergeKeyNode = entry.value;
// break;
// }
// }
// if (mergeKeyNode != null) {
// final mergeValue = mergeKeyNode.value;
// if (mergeValue is YamlMap) {
// map.addAll(convertYamlNode(mergeValue) as Map<String, dynamic>);
// } else if (mergeValue is YamlList) {
// for (final node in mergeValue.nodes) {
// if (node.value is YamlMap) {
// map.addAll(convertYamlNode(node.value) as Map<String, dynamic>);
// }
// }
// }
// }
//
// node.nodes.forEach((key, value) {
// String stringKey;
// if (key is YamlScalar) {
// stringKey = key.value.toString();
// } else {
// stringKey = key.toString();
// }
// map[stringKey] = convertYamlNode(value.value);
// });
// return map;
// } else if (node is YamlList) {
// final list = <dynamic>[];
// for (final item in node.nodes) {
// list.add(convertYamlNode(item.value));
// }
// return list;
// } else if (node is YamlScalar) {
// return node.value;
// }
// return node;
// }
FutureOr<T> handleWatch<T>(Function function) async {
if (kDebugMode) {
final stopwatch = Stopwatch()..start();
final res = await function();
stopwatch.stop();
commonPrint.log('耗时:${stopwatch.elapsedMilliseconds} ms');
return res;
}
return await function();
}
}
final utils = Utils();

View File

@@ -8,6 +8,7 @@ import 'package:archive/archive.dart';
import 'package:fl_clash/clash/clash.dart';
import 'package:fl_clash/common/archive.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/plugins/app.dart';
import 'package:fl_clash/providers/providers.dart';
import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/dialog.dart';
@@ -17,11 +18,10 @@ import 'package:path/path.dart';
import 'package:url_launcher/url_launcher.dart';
import 'common/common.dart';
import 'fragments/profiles/override_profile.dart';
import 'models/models.dart';
import 'views/profiles/override_profile.dart';
class AppController {
bool lastTunEnable = false;
int? lastProfileModified;
final BuildContext context;
@@ -29,19 +29,24 @@ class AppController {
AppController(this.context, WidgetRef ref) : _ref = ref;
setupClashConfigDebounce() {
debouncer.call(FunctionTag.setupClashConfig, () async {
await setupClashConfig();
});
}
updateClashConfigDebounce() {
debouncer.call(DebounceTag.updateClashConfig, () async {
final isPatch = globalState.appState.needApply ? false : true;
await updateClashConfig(isPatch);
debouncer.call(FunctionTag.updateClashConfig, () async {
await updateClashConfig();
});
}
updateGroupsDebounce() {
debouncer.call(DebounceTag.updateGroups, updateGroups);
debouncer.call(FunctionTag.updateGroups, updateGroups);
}
addCheckIpNumDebounce() {
debouncer.call(DebounceTag.addCheckIpNum, () {
debouncer.call(FunctionTag.addCheckIpNum, () {
_ref.read(checkIpNumProvider.notifier).add();
});
}
@@ -49,17 +54,17 @@ class AppController {
applyProfileDebounce({
bool silence = false,
}) {
debouncer.call(DebounceTag.applyProfile, (silence) {
debouncer.call(FunctionTag.applyProfile, (silence) {
applyProfile(silence: silence);
}, args: [silence]);
}
savePreferencesDebounce() {
debouncer.call(DebounceTag.savePreferences, savePreferences);
debouncer.call(FunctionTag.savePreferences, savePreferences);
}
changeProxyDebounce(String groupName, String proxyName) {
debouncer.call(DebounceTag.changeProxy,
debouncer.call(FunctionTag.changeProxy,
(String groupName, String proxyName) async {
await changeProxy(
groupName: groupName,
@@ -70,9 +75,9 @@ class AppController {
}
restartCore() async {
commonPrint.log("restart core");
await clashService?.reStart();
await _initCore();
if (_ref.read(runTimeProvider.notifier).isStart) {
await globalState.handleStart();
}
@@ -243,57 +248,82 @@ class AppController {
);
}
Future<void> updateClashConfig([bool? isPatch]) async {
commonPrint.log("update clash patch: ${isPatch ?? false}");
Future<void> updateClashConfig() async {
final commonScaffoldState = globalState.homeScaffoldKey.currentState;
if (commonScaffoldState?.mounted != true) return;
await commonScaffoldState?.loadingRun(() async {
await _updateClashConfig(
isPatch,
);
await _updateClashConfig();
});
}
Future<void> _updateClashConfig([bool? isPatch]) async {
final profile = _ref.watch(currentProfileProvider);
await _ref.read(currentProfileProvider)?.checkAndUpdate();
final patchConfig = _ref.read(patchClashConfigProvider);
final appSetting = _ref.read(appSettingProvider);
bool enableTun = patchConfig.tun.enable;
if (enableTun != lastTunEnable &&
lastTunEnable == false &&
!Platform.isAndroid) {
Future<void> _updateClashConfig() async {
final updateParams = _ref.read(updateParamsProvider);
final res = await _requestAdmin(updateParams.tun.enable);
if (res.isError) {
return;
}
final realTunEnable = _ref.read(realTunEnableProvider);
final message = await clashCore.updateConfig(
updateParams.copyWith.tun(
enable: realTunEnable,
),
);
if (message.isNotEmpty) throw message;
}
Future<Result<bool>> _requestAdmin(bool enableTun) async {
final realTunEnable = _ref.read(realTunEnableProvider);
if (enableTun != realTunEnable && realTunEnable == false) {
final code = await system.authorizeCore();
switch (code) {
case AuthorizeCode.success:
await restartCore();
return Result.error("");
case AuthorizeCode.none:
break;
case AuthorizeCode.success:
lastTunEnable = enableTun;
await restartCore();
return;
case AuthorizeCode.error:
enableTun = false;
break;
}
}
if (appSetting.openLogs) {
clashCore.startLog();
} else {
clashCore.stopLog();
_ref.read(realTunEnableProvider.notifier).value = enableTun;
return Result.success(enableTun);
}
Future<void> setupClashConfig() async {
final commonScaffoldState = globalState.homeScaffoldKey.currentState;
if (commonScaffoldState?.mounted != true) return;
await commonScaffoldState?.loadingRun(() async {
await _setupClashConfig();
});
}
_setupClashConfig() async {
await _ref.read(currentProfileProvider)?.checkAndUpdate();
final patchConfig = _ref.read(patchClashConfigProvider);
final res = await _requestAdmin(patchConfig.tun.enable);
if (res.isError) {
return;
}
final res = await clashCore.updateConfig(
globalState.getUpdateConfigParams(isPatch),
final realTunEnable = _ref.read(realTunEnableProvider);
final realPatchConfig = patchConfig.copyWith.tun(enable: realTunEnable);
final params = await globalState.getSetupParams(
pathConfig: realPatchConfig,
);
if (isPatch == false) {
_ref.read(needApplyProvider.notifier).value = false;
final message = await clashCore.setupConfig(params);
lastProfileModified = await _ref.read(
currentProfileProvider.select(
(state) => state?.profileLastModified,
),
);
if (message.isNotEmpty) {
throw message;
}
if (res.isNotEmpty) throw res;
lastTunEnable = enableTun;
lastProfileModified = await profile?.profileLastModified;
}
Future _applyProfile() async {
await clashCore.requestGc();
await updateClashConfig();
await setupClashConfig();
await updateGroups();
await updateProviders();
}
@@ -314,6 +344,10 @@ class AppController {
handleChangeProfile() {
_ref.read(delayDataSourceProvider.notifier).value = {};
applyProfile();
_ref.read(logsProvider.notifier).value = FixedList(500);
_ref.read(requestsProvider.notifier).value = FixedList(500);
globalState.cacheHeightMap = {};
globalState.cacheScrollPosition = {};
}
updateBrightness(Brightness brightness) {
@@ -383,6 +417,9 @@ class AppController {
}
handleBackOrExit() async {
if (_ref.read(backBlockProvider)) {
return;
}
if (_ref.read(appSettingProvider).minimizeOnExit) {
if (system.isDesktop) {
await savePreferencesDebounce();
@@ -393,13 +430,24 @@ class AppController {
}
}
backBlock() {
_ref.read(backBlockProvider.notifier).value = true;
}
unBackBlock() {
_ref.read(backBlockProvider.notifier).value = false;
}
handleExit() async {
Future.delayed(commonDuration, () {
system.exit();
});
try {
await updateStatus(false);
await savePreferences();
await system.setMacOSDns(true);
await proxy?.stopProxy();
await clashCore.shutdown();
await clashService?.destroy();
await proxy?.stopProxy();
await savePreferences();
} finally {
system.exit();
}
@@ -487,20 +535,21 @@ class AppController {
Future<void> _initCore() async {
final isInit = await clashCore.isInit;
if (!isInit) {
await clashCore.init();
await clashCore.setState(
globalState.getCoreState(),
);
await clashCore.init();
}
await applyProfile();
}
init() async {
await _handlePreference();
await _handlerDisclaimer();
FlutterError.onError = (details) {
commonPrint.log(details.stack.toString());
};
updateTray(true);
await _initCore();
await _initStatus();
updateTray(true);
autoLaunch?.updateStatus(
_ref.read(appSettingProvider).autoLaunch,
);
@@ -511,6 +560,8 @@ class AppController {
} else {
window?.hide();
}
await _handlePreference();
await _handlerDisclaimer();
_ref.read(initProvider.notifier).value = true;
}
@@ -688,10 +739,16 @@ class AppController {
return List.of(proxies)
..sort(
(a, b) {
final aDelay =
_ref.read(getDelayProvider(proxyName: a.name, testUrl: testUrl));
final bDelay =
_ref.read(getDelayProvider(proxyName: b.name, testUrl: testUrl));
final aDelay = _ref.read(getDelayProvider(
proxyName: a.name,
testUrl: testUrl,
));
final bDelay = _ref.read(
getDelayProvider(
proxyName: b.name,
testUrl: testUrl,
),
);
if (aDelay == null && bDelay == null) {
return 0;
}
@@ -719,21 +776,17 @@ class AppController {
clearEffect(String profileId) async {
final profilePath = await appPath.getProfilePath(profileId);
final providersPath = await appPath.getProvidersPath(profileId);
final providersDirPath = await appPath.getProvidersDirPath(profileId);
return await Isolate.run(() async {
if (profilePath != null) {
final profileFile = File(profilePath);
final isExists = await profileFile.exists();
if (isExists) {
profileFile.delete(recursive: true);
}
final profileFile = File(profilePath);
final isExists = await profileFile.exists();
if (isExists) {
profileFile.delete(recursive: true);
}
if (providersPath != null) {
final providersFileDir = File(providersPath);
final isExists = await providersFileDir.exists();
if (isExists) {
providersFileDir.delete(recursive: true);
}
final providersFileDir = File(providersDirPath);
final providersFileIsExists = await providersFileDir.exists();
if (providersFileIsExists) {
providersFileDir.delete(recursive: true);
}
});
}
@@ -752,6 +805,17 @@ class AppController {
);
}
Future<List<Package>> getPackages() async {
if (_ref.read(isMobileViewProvider)) {
await Future.delayed(commonDuration);
}
if (_ref.read(packagesProvider).isEmpty) {
_ref.read(packagesProvider.notifier).value =
await app?.getPackages() ?? [];
}
return _ref.read(packagesProvider);
}
updateStart() {
updateStatus(!_ref.read(runTimeProvider.notifier).isStart);
}
@@ -940,14 +1004,18 @@ class AppController {
}
_recovery(Config config, RecoveryOption recoveryOption) {
final recoveryStrategy = _ref.read(appSettingProvider.select(
(state) => state.recoveryStrategy,
));
final profiles = config.profiles;
final oldProfiles = List.from(globalState.config.profiles);
_ref.read(profilesProvider.notifier).value = profiles;
for (final profile in oldProfiles) {
_ref.read(profilesProvider.notifier).setProfile(
profile,
force: false,
);
if (recoveryStrategy == RecoveryStrategy.override) {
_ref.read(profilesProvider.notifier).value = profiles;
} else {
for (final profile in profiles) {
_ref.read(profilesProvider.notifier).setProfile(
profile,
);
}
}
final onlyProfiles = recoveryOption == RecoveryOption.onlyProfiles;
if (!onlyProfiles) {
@@ -965,6 +1033,7 @@ class AppController {
_ref.read(overrideDnsProvider.notifier).value = config.overrideDns;
_ref.read(networkSettingProvider.notifier).value = config.networkProps;
_ref.read(hotKeyActionsProvider.notifier).value = config.hotKeyActions;
_ref.read(scriptStateProvider.notifier).value = config.scriptProps;
}
final currentProfile = _ref.read(currentProfileProvider);
if (currentProfile == null) {

View File

@@ -2,7 +2,7 @@
import 'dart:io';
import 'package:fl_clash/fragments/dashboard/widgets/widgets.dart';
import 'package:fl_clash/views/dashboard/widgets/widgets.dart';
import 'package:fl_clash/widgets/widgets.dart';
import 'package:flutter/services.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
@@ -118,7 +118,12 @@ enum AccessSortType { none, name, time }
enum ProfileType { file, url }
enum ResultType { success, error }
enum ResultType {
@JsonValue(0)
success,
@JsonValue(-1)
error,
}
enum AppMessageType {
log,
@@ -164,9 +169,13 @@ enum DnsMode {
enum ExternalControllerStatus {
@JsonValue("")
close,
close(""),
@JsonValue("127.0.0.1:9090")
open
open("127.0.0.1:9090");
final String value;
const ExternalControllerStatus(this.value);
}
enum KeyboardModifier {
@@ -248,6 +257,7 @@ enum ActionMethod {
shutdown,
validateConfig,
updateConfig,
getConfig,
getProxies,
changeProxy,
getTraffic,
@@ -256,6 +266,7 @@ enum ActionMethod {
asyncTestDelay,
getConnections,
closeConnections,
resetConnections,
closeConnection,
getExternalProviders,
getExternalProvider,
@@ -268,12 +279,10 @@ enum ActionMethod {
stopListener,
getCountryCode,
getMemory,
getProfile,
crash,
setupConfig,
///Android,
setFdMap,
setProcessMap,
setState,
startTun,
stopTun,
@@ -291,8 +300,10 @@ enum WindowsHelperServiceStatus {
running,
}
enum DebounceTag {
enum FunctionTag {
updateClashConfig,
setupClashConfig,
updateStatus,
updateGroups,
addCheckIpNum,
applyProfile,
@@ -307,6 +318,8 @@ enum DebounceTag {
updatePageIndex,
pageChange,
proxiesTabChange,
logs,
requests,
}
enum DashboardWidget {
@@ -470,3 +483,24 @@ enum RuleTarget {
DIRECT,
REJECT,
}
enum RecoveryStrategy {
compatible,
override,
}
enum CacheTag {
logs,
rules,
requests,
}
enum Language {
yaml,
javaScript,
}
enum ImportOption {
file,
url,
}

View File

@@ -1,430 +0,0 @@
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/providers/providers.dart';
import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/widgets.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class LogLevelItem extends ConsumerWidget {
const LogLevelItem({super.key});
@override
Widget build(BuildContext context, ref) {
final logLevel =
ref.watch(patchClashConfigProvider.select((state) => state.logLevel));
return ListItem<LogLevel>.options(
leading: const Icon(Icons.info_outline),
title: Text(appLocalizations.logLevel),
subtitle: Text(logLevel.name),
delegate: OptionsDelegate<LogLevel>(
title: appLocalizations.logLevel,
options: LogLevel.values,
onChanged: (LogLevel? value) {
if (value == null) {
return;
}
ref.read(patchClashConfigProvider.notifier).updateState(
(state) => state.copyWith(
logLevel: value,
),
);
},
textBuilder: (logLevel) => logLevel.name,
value: logLevel,
),
);
}
}
class UaItem extends ConsumerWidget {
const UaItem({super.key});
@override
Widget build(BuildContext context, ref) {
final globalUa =
ref.watch(patchClashConfigProvider.select((state) => state.globalUa));
return ListItem<String?>.options(
leading: const Icon(Icons.computer_outlined),
title: const Text("UA"),
subtitle: Text(globalUa ?? appLocalizations.defaultText),
delegate: OptionsDelegate<String?>(
title: "UA",
options: [
null,
"clash-verge/v1.6.6",
"ClashforWindows/0.19.23",
],
value: globalUa,
onChanged: (value) {
ref.read(patchClashConfigProvider.notifier).updateState(
(state) => state.copyWith(
globalUa: value,
),
);
},
textBuilder: (ua) => ua ?? appLocalizations.defaultText,
),
);
}
}
class KeepAliveIntervalItem extends ConsumerWidget {
const KeepAliveIntervalItem({super.key});
@override
Widget build(BuildContext context, ref) {
final keepAliveInterval = ref.watch(
patchClashConfigProvider.select((state) => state.keepAliveInterval));
return ListItem.input(
leading: const Icon(Icons.timer_outlined),
title: Text(appLocalizations.keepAliveIntervalDesc),
subtitle: Text("$keepAliveInterval ${appLocalizations.seconds}"),
delegate: InputDelegate(
title: appLocalizations.keepAliveIntervalDesc,
suffixText: appLocalizations.seconds,
resetValue: "$defaultKeepAliveInterval",
value: "$keepAliveInterval",
onChanged: (String? value) {
if (value == null) {
return;
}
globalState.safeRun(
() {
final intValue = int.parse(value);
if (intValue <= 0) {
throw "Invalid keepAliveInterval";
}
ref.read(patchClashConfigProvider.notifier).updateState(
(state) => state.copyWith(
keepAliveInterval: intValue,
),
);
},
silence: false,
title: appLocalizations.keepAliveIntervalDesc,
);
},
),
);
}
}
class TestUrlItem extends ConsumerWidget {
const TestUrlItem({super.key});
@override
Widget build(BuildContext context, ref) {
final testUrl =
ref.watch(appSettingProvider.select((state) => state.testUrl));
return ListItem.input(
leading: const Icon(Icons.timeline),
title: Text(appLocalizations.testUrl),
subtitle: Text(testUrl),
delegate: InputDelegate(
resetValue: defaultTestUrl,
title: appLocalizations.testUrl,
value: testUrl,
onChanged: (String? value) {
if (value == null) {
return;
}
globalState.safeRun(
() {
if (!value.isUrl) {
throw "Invalid url";
}
ref.read(appSettingProvider.notifier).updateState(
(state) => state.copyWith(
testUrl: value,
),
);
},
silence: false,
title: appLocalizations.testUrl,
);
}),
);
}
}
class MixedPortItem extends ConsumerWidget {
const MixedPortItem({super.key});
@override
Widget build(BuildContext context, ref) {
final mixedPort =
ref.watch(patchClashConfigProvider.select((state) => state.mixedPort));
return ListItem.input(
leading: const Icon(Icons.adjust_outlined),
title: Text(appLocalizations.proxyPort),
subtitle: Text("$mixedPort"),
delegate: InputDelegate(
title: appLocalizations.proxyPort,
value: "$mixedPort",
onChanged: (String? value) {
if (value == null) {
return;
}
globalState.safeRun(
() {
final mixedPort = int.parse(value);
if (mixedPort < 1024 || mixedPort > 49151) {
throw "Invalid port";
}
ref.read(patchClashConfigProvider.notifier).updateState(
(state) => state.copyWith(
mixedPort: mixedPort,
),
);
},
silence: false,
title: appLocalizations.proxyPort,
);
},
resetValue: "$defaultMixedPort",
),
);
}
}
class HostsItem extends StatelessWidget {
const HostsItem({super.key});
@override
Widget build(BuildContext context) {
return ListItem.open(
leading: const Icon(Icons.view_list_outlined),
title: const Text("Hosts"),
subtitle: Text(appLocalizations.hostsDesc),
delegate: OpenDelegate(
blur: false,
title: "Hosts",
widget: Consumer(
builder: (_, ref, __) {
final hosts = ref
.watch(patchClashConfigProvider.select((state) => state.hosts));
return MapInputPage(
title: "Hosts",
map: hosts,
titleBuilder: (item) => Text(item.key),
subtitleBuilder: (item) => Text(item.value),
onChange: (value) {
ref.read(patchClashConfigProvider.notifier).updateState(
(state) => state.copyWith(
hosts: value,
),
);
},
);
},
),
),
);
}
}
class Ipv6Item extends ConsumerWidget {
const Ipv6Item({super.key});
@override
Widget build(BuildContext context, ref) {
final ipv6 =
ref.watch(patchClashConfigProvider.select((state) => state.ipv6));
return ListItem.switchItem(
leading: const Icon(Icons.water_outlined),
title: const Text("IPv6"),
subtitle: Text(appLocalizations.ipv6Desc),
delegate: SwitchDelegate(
value: ipv6,
onChanged: (bool value) async {
ref.read(patchClashConfigProvider.notifier).updateState(
(state) => state.copyWith(
ipv6: value,
),
);
},
),
);
}
}
class AllowLanItem extends ConsumerWidget {
const AllowLanItem({super.key});
@override
Widget build(BuildContext context, ref) {
final allowLan =
ref.watch(patchClashConfigProvider.select((state) => state.allowLan));
return ListItem.switchItem(
leading: const Icon(Icons.device_hub),
title: Text(appLocalizations.allowLan),
subtitle: Text(appLocalizations.allowLanDesc),
delegate: SwitchDelegate(
value: allowLan,
onChanged: (bool value) async {
ref.read(patchClashConfigProvider.notifier).updateState(
(state) => state.copyWith(
allowLan: value,
),
);
},
),
);
}
}
class UnifiedDelayItem extends ConsumerWidget {
const UnifiedDelayItem({super.key});
@override
Widget build(BuildContext context, ref) {
final unifiedDelay = ref
.watch(patchClashConfigProvider.select((state) => state.unifiedDelay));
return ListItem.switchItem(
leading: const Icon(Icons.compress_outlined),
title: Text(appLocalizations.unifiedDelay),
subtitle: Text(appLocalizations.unifiedDelayDesc),
delegate: SwitchDelegate(
value: unifiedDelay,
onChanged: (bool value) async {
ref.read(patchClashConfigProvider.notifier).updateState(
(state) => state.copyWith(
unifiedDelay: value,
),
);
},
),
);
}
}
class FindProcessItem extends ConsumerWidget {
const FindProcessItem({super.key});
@override
Widget build(BuildContext context, ref) {
final findProcess = ref.watch(patchClashConfigProvider
.select((state) => state.findProcessMode == FindProcessMode.always));
return ListItem.switchItem(
leading: const Icon(Icons.polymer_outlined),
title: Text(appLocalizations.findProcessMode),
subtitle: Text(appLocalizations.findProcessModeDesc),
delegate: SwitchDelegate(
value: findProcess,
onChanged: (bool value) async {
ref.read(patchClashConfigProvider.notifier).updateState(
(state) => state.copyWith(
findProcessMode:
value ? FindProcessMode.always : FindProcessMode.off,
),
);
},
),
);
}
}
class TcpConcurrentItem extends ConsumerWidget {
const TcpConcurrentItem({super.key});
@override
Widget build(BuildContext context, ref) {
final tcpConcurrent = ref
.watch(patchClashConfigProvider.select((state) => state.tcpConcurrent));
return ListItem.switchItem(
leading: const Icon(Icons.double_arrow_outlined),
title: Text(appLocalizations.tcpConcurrent),
subtitle: Text(appLocalizations.tcpConcurrentDesc),
delegate: SwitchDelegate(
value: tcpConcurrent,
onChanged: (value) async {
ref.read(patchClashConfigProvider.notifier).updateState(
(state) => state.copyWith(
tcpConcurrent: value,
),
);
},
),
);
}
}
class GeodataLoaderItem extends ConsumerWidget {
const GeodataLoaderItem({super.key});
@override
Widget build(BuildContext context, ref) {
final isMemconservative = ref.watch(patchClashConfigProvider.select(
(state) => state.geodataLoader == GeodataLoader.memconservative));
return ListItem.switchItem(
leading: const Icon(Icons.memory),
title: Text(appLocalizations.geodataLoader),
subtitle: Text(appLocalizations.geodataLoaderDesc),
delegate: SwitchDelegate(
value: isMemconservative,
onChanged: (bool value) async {
ref.read(patchClashConfigProvider.notifier).updateState(
(state) => state.copyWith(
geodataLoader: value
? GeodataLoader.memconservative
: GeodataLoader.standard,
),
);
},
),
);
}
}
class ExternalControllerItem extends ConsumerWidget {
const ExternalControllerItem({super.key});
@override
Widget build(BuildContext context, ref) {
final hasExternalController = ref.watch(patchClashConfigProvider.select(
(state) => state.externalController == ExternalControllerStatus.open));
return ListItem.switchItem(
leading: const Icon(Icons.api_outlined),
title: Text(appLocalizations.externalController),
subtitle: Text(appLocalizations.externalControllerDesc),
delegate: SwitchDelegate(
value: hasExternalController,
onChanged: (bool value) async {
ref.read(patchClashConfigProvider.notifier).updateState(
(state) => state.copyWith(
externalController: value
? ExternalControllerStatus.open
: ExternalControllerStatus.close,
),
);
},
),
);
}
}
final generalItems = <Widget>[
LogLevelItem(),
UaItem(),
if (system.isDesktop) KeepAliveIntervalItem(),
TestUrlItem(),
MixedPortItem(),
HostsItem(),
Ipv6Item(),
AllowLanItem(),
UnifiedDelayItem(),
FindProcessItem(),
TcpConcurrentItem(),
GeodataLoaderItem(),
ExternalControllerItem(),
]
.separated(
const Divider(
height: 0,
),
)
.toList();

View File

@@ -1,210 +0,0 @@
import 'dart:io';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/plugins/app.dart';
import 'package:fl_clash/providers/config.dart';
import 'package:fl_clash/widgets/widgets.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class ConnectionItem extends ConsumerWidget {
final Connection connection;
final Function(String)? onClickKeyword;
final Widget? trailing;
const ConnectionItem({
super.key,
required this.connection,
this.onClickKeyword,
this.trailing,
});
Future<ImageProvider?> _getPackageIcon(Connection connection) async {
return await app?.getPackageIcon(connection.metadata.process);
}
String _getSourceText(Connection connection) {
final metadata = connection.metadata;
if (metadata.process.isEmpty) {
return connection.start.lastUpdateTimeDesc;
}
return "${metadata.process} · ${connection.start.lastUpdateTimeDesc}";
}
@override
Widget build(BuildContext context, ref) {
final value = ref.watch(
patchClashConfigProvider.select(
(state) =>
state.findProcessMode == FindProcessMode.always &&
Platform.isAndroid,
),
);
final title = Text(
connection.desc,
style: context.textTheme.bodyLarge,
);
final subTitle = Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(
height: 8,
),
Text(
_getSourceText(connection),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(
height: 8,
),
Wrap(
runSpacing: 6,
spacing: 6,
children: [
for (final chain in connection.chains)
CommonChip(
label: chain,
onPressed: () {
if (onClickKeyword == null) return;
onClickKeyword!(chain);
},
),
],
),
],
);
return CommonPopupBox(
targetBuilder: (open) {
return ListItem(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 4,
),
tileTitleAlignment: ListTileTitleAlignment.titleHeight,
leading: value
? GestureDetector(
onTap: () {
if (onClickKeyword == null) return;
final process = connection.metadata.process;
if (process.isEmpty) return;
onClickKeyword!(process);
},
child: Container(
margin: const EdgeInsets.only(top: 4),
width: 48,
height: 48,
child: FutureBuilder<ImageProvider?>(
future: _getPackageIcon(connection),
builder: (_, snapshot) {
if (!snapshot.hasData && snapshot.data == null) {
return Container();
} else {
return Image(
image: snapshot.data!,
gaplessPlayback: true,
width: 48,
height: 48,
);
}
},
),
),
)
: null,
title: title,
subtitle: subTitle,
trailing: trailing,
);
// return InkWell(
// child: GestureDetector(
// onLongPressStart: (details) {
// if (!system.isDesktop) {
// return;
// }
// open(
// offset: details.localPosition.translate(
// 0,
// -12,
// ),
// );
// },
// onSecondaryTapDown: (details) {
// if (!system.isDesktop) {
// return;
// }
// open(
// offset: details.localPosition.translate(
// 0,
// -12,
// ),
// );
// },
// child: ListItem(
// padding: const EdgeInsets.symmetric(
// horizontal: 16,
// vertical: 4,
// ),
// tileTitleAlignment: ListTileTitleAlignment.titleHeight,
// leading: value
// ? GestureDetector(
// onTap: () {
// if (onClickKeyword == null) return;
// final process = connection.metadata.process;
// if (process.isEmpty) return;
// onClickKeyword!(process);
// },
// child: Container(
// margin: const EdgeInsets.only(top: 4),
// width: 48,
// height: 48,
// child: FutureBuilder<ImageProvider?>(
// future: _getPackageIcon(connection),
// builder: (_, snapshot) {
// if (!snapshot.hasData && snapshot.data == null) {
// return Container();
// } else {
// return Image(
// image: snapshot.data!,
// gaplessPlayback: true,
// width: 48,
// height: 48,
// );
// }
// },
// ),
// ),
// )
// : null,
// title: title,
// subtitle: subTitle,
// trailing: trailing,
// ),
// ),
// onTap: () {},
// );
},
popup: CommonPopupMenu(
minWidth: 160,
items: [
PopupMenuItemData(
label: "编辑规则",
onPressed: () {
// _handleShowEditExtendPage(context);
},
),
PopupMenuItemData(
label: "设置直连",
onPressed: () {},
),
PopupMenuItemData(
label: "一键屏蔽",
onPressed: () {},
),
],
),
);
}
}

View File

@@ -1,138 +0,0 @@
import 'dart:math';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/providers/providers.dart';
import 'package:fl_clash/widgets/widgets.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'widgets/start_button.dart';
class DashboardFragment extends ConsumerStatefulWidget {
const DashboardFragment({super.key});
@override
ConsumerState<DashboardFragment> createState() => _DashboardFragmentState();
}
class _DashboardFragmentState extends ConsumerState<DashboardFragment>
with PageMixin {
final key = GlobalKey<SuperGridState>();
@override
initState() {
ref.listenManual(
isCurrentPageProvider(PageLabel.dashboard),
(prev, next) {
if (prev != next && next == true) {
initPageState();
}
},
fireImmediately: true,
);
return super.initState();
}
@override
Widget? get floatingActionButton => const StartButton();
@override
List<Widget> get actions => [
ValueListenableBuilder(
valueListenable: key.currentState!.addedChildrenNotifier,
builder: (_, addedChildren, child) {
return ValueListenableBuilder(
valueListenable: key.currentState!.isEditNotifier,
builder: (_, isEdit, child) {
if (!isEdit || addedChildren.isEmpty) {
return Container();
}
return child!;
},
child: child,
);
},
child: IconButton(
onPressed: () {
key.currentState!.showAddModal();
},
icon: Icon(
Icons.add_circle,
),
),
),
IconButton(
icon: ValueListenableBuilder(
valueListenable: key.currentState!.isEditNotifier,
builder: (_, isEdit, ___) {
return isEdit
? Icon(Icons.save)
: Icon(
Icons.edit,
);
},
),
onPressed: () {
key.currentState!.isEditNotifier.value =
!key.currentState!.isEditNotifier.value;
},
),
];
_handleSave(List<GridItem> girdItems, WidgetRef ref) {
final dashboardWidgets = girdItems
.map(
(item) => DashboardWidget.getDashboardWidget(item),
)
.toList();
ref.read(appSettingProvider.notifier).updateState(
(state) => state.copyWith(dashboardWidgets: dashboardWidgets),
);
}
@override
Widget build(BuildContext context) {
final dashboardState = ref.watch(dashboardStateProvider);
final columns = max(4 * ((dashboardState.viewWidth / 320).ceil()), 8);
return Align(
alignment: Alignment.topCenter,
child: SingleChildScrollView(
padding: const EdgeInsets.all(16).copyWith(
bottom: 88,
),
child: SuperGrid(
key: key,
crossAxisCount: columns,
crossAxisSpacing: 16.ap,
mainAxisSpacing: 16.ap,
children: [
...dashboardState.dashboardWidgets
.where(
(item) => item.platforms.contains(
SupportPlatform.currentPlatform,
),
)
.map(
(item) => item.widget,
),
],
onSave: (girdItems) {
_handleSave(girdItems, ref);
},
addedItemsBuilder: (girdItems) {
return DashboardWidget.values
.where(
(item) =>
!girdItems.contains(item.widget) &&
item.platforms.contains(
SupportPlatform.currentPlatform,
),
)
.map((item) => item.widget)
.toList();
},
),
),
);
}
}

View File

@@ -1,137 +0,0 @@
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/providers/providers.dart';
import 'package:fl_clash/state.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class StartButton extends StatefulWidget {
const StartButton({super.key});
@override
State<StartButton> createState() => _StartButtonState();
}
class _StartButtonState extends State<StartButton>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
bool isStart = false;
@override
void initState() {
super.initState();
isStart = globalState.appState.runTime != null;
_controller = AnimationController(
vsync: this,
value: isStart ? 1 : 0,
duration: const Duration(milliseconds: 200),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
handleSwitchStart() {
if (isStart == globalState.appState.isStart) {
isStart = !isStart;
updateController();
globalState.appController.updateStatus(isStart);
}
}
updateController() {
if (isStart) {
_controller.forward();
} else {
_controller.reverse();
}
}
@override
Widget build(BuildContext context) {
return Consumer(
builder: (_, ref, child) {
final state = ref.watch(startButtonSelectorStateProvider);
if (!state.isInit || !state.hasProfile) {
return Container();
}
ref.listenManual(
runTimeProvider.select((state) => state != null),
(prev, next) {
if (next != isStart) {
isStart = next;
updateController();
}
},
fireImmediately: true,
);
final textWidth = globalState.measure
.computeTextSize(
Text(
utils.getTimeDifference(
DateTime.now(),
),
style: context.textTheme.titleMedium?.toSoftBold,
),
)
.width +
16;
return AnimatedBuilder(
animation: _controller.view,
builder: (_, child) {
return SizedBox(
width: 56 + textWidth * _controller.value,
height: 56,
child: FloatingActionButton(
heroTag: null,
onPressed: () {
handleSwitchStart();
},
child: Row(
children: [
Container(
width: 56,
height: 56,
alignment: Alignment.center,
child: AnimatedIcon(
icon: AnimatedIcons.play_pause,
progress: _controller,
),
),
Expanded(
child: ClipRect(
child: OverflowBox(
maxWidth: textWidth,
child: Container(
alignment: Alignment.centerLeft,
child: child!,
),
),
),
),
],
),
),
);
},
child: child,
);
},
child: Consumer(
builder: (_, ref, __) {
final runTime = ref.watch(runTimeProvider);
final text = utils.getTimeText(runTime);
return Text(
text,
style: Theme.of(context).textTheme.titleMedium?.toSoftBold.copyWith(
color: context.colorScheme.onPrimaryContainer
),
);
},
),
);
}
}

View File

@@ -1,174 +0,0 @@
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/fragments/proxies/list.dart';
import 'package:fl_clash/fragments/proxies/providers.dart';
import 'package:fl_clash/providers/providers.dart';
import 'package:fl_clash/widgets/widgets.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'common.dart';
import 'setting.dart';
import 'tab.dart';
class ProxiesFragment extends ConsumerStatefulWidget {
const ProxiesFragment({super.key});
@override
ConsumerState<ProxiesFragment> createState() => _ProxiesFragmentState();
}
class _ProxiesFragmentState extends ConsumerState<ProxiesFragment>
with PageMixin {
final GlobalKey<ProxiesTabFragmentState> _proxiesTabKey = GlobalKey();
bool _hasProviders = false;
bool _isTab = false;
@override
get actions => [
if (_hasProviders)
IconButton(
onPressed: () {
showExtend(
context,
builder: (_, type) {
return ProvidersView(
type: type,
);
},
);
},
icon: const Icon(
Icons.poll_outlined,
),
),
_isTab
? IconButton(
onPressed: () {
_proxiesTabKey.currentState?.scrollToGroupSelected();
},
icon: const Icon(
Icons.adjust_outlined,
),
)
: IconButton(
onPressed: () {
showExtend(
context,
builder: (_, type) {
return AdaptiveSheetScaffold(
type: type,
body: const _IconConfigView(),
title: appLocalizations.iconConfiguration,
);
},
);
},
icon: const Icon(
Icons.style_outlined,
),
),
IconButton(
onPressed: () {
showSheet(
context: context,
props: SheetProps(
isScrollControlled: true,
),
builder: (_, type) {
return AdaptiveSheetScaffold(
type: type,
body: const ProxiesSetting(),
title: appLocalizations.settings,
);
},
);
},
icon: const Icon(
Icons.tune,
),
)
];
@override
get floatingActionButton => _isTab
? DelayTestButton(
onClick: () async {
await delayTest(
currentTabProxies,
currentTabTestUrl,
);
},
)
: null;
@override
void initState() {
ref.listenManual(
proxiesActionsStateProvider,
fireImmediately: true,
(prev, next) {
if (prev == next) {
return;
}
if (next.pageLabel == PageLabel.proxies) {
_hasProviders = next.hasProviders;
_isTab = next.type == ProxiesType.tab;
initPageState();
return;
}
},
);
super.initState();
}
@override
Widget build(BuildContext context) {
final proxiesType =
ref.watch(proxiesStyleSettingProvider.select((state) => state.type));
return switch (proxiesType) {
ProxiesType.tab => ProxiesTabFragment(
key: _proxiesTabKey,
),
ProxiesType.list => const ProxiesListFragment(),
};
}
}
class _IconConfigView extends ConsumerWidget {
const _IconConfigView();
@override
Widget build(BuildContext context, WidgetRef ref) {
final iconMap =
ref.watch(proxiesStyleSettingProvider.select((state) => state.iconMap));
return MapInputPage(
title: appLocalizations.iconConfiguration,
map: iconMap,
keyLabel: appLocalizations.regExp,
valueLabel: appLocalizations.icon,
titleBuilder: (item) => Text(item.key),
leadingBuilder: (item) => Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16),
),
clipBehavior: Clip.antiAlias,
child: CommonTargetIcon(
src: item.value,
size: 42,
),
),
subtitleBuilder: (item) => Text(
item.value,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
onChange: (value) {
ref.read(proxiesStyleSettingProvider.notifier).updateState(
(state) => state.copyWith(
iconMap: value,
),
);
},
);
}
}

View File

@@ -20,7 +20,25 @@ typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
class MessageLookup extends MessageLookupByLibrary {
String get localeName => 'en';
static String m0(count) => "${count} items have been selected";
static String m0(label) =>
"Are you sure you want to delete the selected ${label}?";
static String m1(label) =>
"Are you sure you want to delete the current ${label}?";
static String m2(label) => "${label} cannot be empty";
static String m3(label) => "Current ${label} already exists";
static String m4(label) => "No ${label} at the moment";
static String m5(label) => "${label} must be a number";
static String m6(label) => "${label} must be between 1024 and 49151";
static String m7(count) => "${count} items have been selected";
static String m8(label) => "${label} must be a url";
final messages = _notInlinedMessages(_notInlinedMessages);
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
@@ -36,9 +54,6 @@ class MessageLookup extends MessageLookupByLibrary {
"The selected application will be excluded from VPN",
),
"account": MessageLookupByLibrary.simpleMessage("Account"),
"accountTip": MessageLookupByLibrary.simpleMessage(
"Account cannot be empty",
),
"action": MessageLookupByLibrary.simpleMessage("Action"),
"action_mode": MessageLookupByLibrary.simpleMessage("Switch mode"),
"action_proxy": MessageLookupByLibrary.simpleMessage("System proxy"),
@@ -108,6 +123,9 @@ class MessageLookup extends MessageLookupByLibrary {
"autoRunDesc": MessageLookupByLibrary.simpleMessage(
"Auto run when the application is opened",
),
"autoSetSystemDns": MessageLookupByLibrary.simpleMessage(
"Auto set system DNS",
),
"autoUpdate": MessageLookupByLibrary.simpleMessage("Auto update"),
"autoUpdateInterval": MessageLookupByLibrary.simpleMessage(
"Auto update interval (minutes)",
@@ -149,9 +167,7 @@ class MessageLookup extends MessageLookupByLibrary {
"clearData": MessageLookupByLibrary.simpleMessage("Clear Data"),
"clipboardExport": MessageLookupByLibrary.simpleMessage("Export clipboard"),
"clipboardImport": MessageLookupByLibrary.simpleMessage("Clipboard import"),
"colorExists": MessageLookupByLibrary.simpleMessage(
"Current color already exists",
),
"color": MessageLookupByLibrary.simpleMessage("Color"),
"colorSchemes": MessageLookupByLibrary.simpleMessage("Color schemes"),
"columns": MessageLookupByLibrary.simpleMessage("Columns"),
"compatible": MessageLookupByLibrary.simpleMessage("Compatibility mode"),
@@ -164,10 +180,8 @@ class MessageLookup extends MessageLookupByLibrary {
"View current connections data",
),
"connectivity": MessageLookupByLibrary.simpleMessage("Connectivity"),
"contactMe": MessageLookupByLibrary.simpleMessage("Contact me"),
"content": MessageLookupByLibrary.simpleMessage("Content"),
"contentEmptyTip": MessageLookupByLibrary.simpleMessage(
"Content cannot be empty",
),
"contentScheme": MessageLookupByLibrary.simpleMessage("Content"),
"copy": MessageLookupByLibrary.simpleMessage("Copy"),
"copyEnvVar": MessageLookupByLibrary.simpleMessage(
@@ -195,15 +209,8 @@ class MessageLookup extends MessageLookupByLibrary {
"delay": MessageLookupByLibrary.simpleMessage("Delay"),
"delaySort": MessageLookupByLibrary.simpleMessage("Sort by delay"),
"delete": MessageLookupByLibrary.simpleMessage("Delete"),
"deleteColorTip": MessageLookupByLibrary.simpleMessage(
"Are you sure you want to delete the current color?",
),
"deleteProfileTip": MessageLookupByLibrary.simpleMessage(
"Sure you want to delete the current profile?",
),
"deleteRuleTip": MessageLookupByLibrary.simpleMessage(
"Are you sure you want to delete the selected rule?",
),
"deleteMultipTip": m0,
"deleteTip": m1,
"desc": MessageLookupByLibrary.simpleMessage(
"A multi-platform proxy client based on ClashMeta, simple and easy to use, open-source and ad-free.",
),
@@ -235,6 +242,7 @@ class MessageLookup extends MessageLookupByLibrary {
"domain": MessageLookupByLibrary.simpleMessage("Domain"),
"download": MessageLookupByLibrary.simpleMessage("Download"),
"edit": MessageLookupByLibrary.simpleMessage("Edit"),
"emptyTip": m2,
"en": MessageLookupByLibrary.simpleMessage("English"),
"enableOverride": MessageLookupByLibrary.simpleMessage("Enable override"),
"entries": MessageLookupByLibrary.simpleMessage(" entries"),
@@ -242,6 +250,7 @@ class MessageLookup extends MessageLookupByLibrary {
"excludeDesc": MessageLookupByLibrary.simpleMessage(
"When the app is in the background, the app is hidden from the recent task",
),
"existsTip": m3,
"exit": MessageLookupByLibrary.simpleMessage("Exit"),
"expand": MessageLookupByLibrary.simpleMessage("Standard"),
"expirationTime": MessageLookupByLibrary.simpleMessage("Expiration time"),
@@ -317,7 +326,10 @@ class MessageLookup extends MessageLookupByLibrary {
"Icon configuration",
),
"iconStyle": MessageLookupByLibrary.simpleMessage("Icon style"),
"import": MessageLookupByLibrary.simpleMessage("Import"),
"importFile": MessageLookupByLibrary.simpleMessage("Import from file"),
"importFromURL": MessageLookupByLibrary.simpleMessage("Import from URL"),
"importUrl": MessageLookupByLibrary.simpleMessage("Import from URL"),
"infiniteTime": MessageLookupByLibrary.simpleMessage("Long term effective"),
"init": MessageLookupByLibrary.simpleMessage("Init"),
"inputCorrectHotkey": MessageLookupByLibrary.simpleMessage(
@@ -327,6 +339,7 @@ class MessageLookup extends MessageLookupByLibrary {
"Intelligent selection",
),
"internet": MessageLookupByLibrary.simpleMessage("Internet"),
"interval": MessageLookupByLibrary.simpleMessage("Interval"),
"intranetIP": MessageLookupByLibrary.simpleMessage("Intranet IP"),
"ipcidr": MessageLookupByLibrary.simpleMessage("Ipcidr"),
"ipv6Desc": MessageLookupByLibrary.simpleMessage(
@@ -341,9 +354,6 @@ class MessageLookup extends MessageLookupByLibrary {
"Tcp keep alive interval",
),
"key": MessageLookupByLibrary.simpleMessage("Key"),
"keyExists": MessageLookupByLibrary.simpleMessage(
"The current key already exists",
),
"language": MessageLookupByLibrary.simpleMessage("Language"),
"layout": MessageLookupByLibrary.simpleMessage("Layout"),
"light": MessageLookupByLibrary.simpleMessage("Light"),
@@ -363,6 +373,7 @@ class MessageLookup extends MessageLookupByLibrary {
),
"logs": MessageLookupByLibrary.simpleMessage("Logs"),
"logsDesc": MessageLookupByLibrary.simpleMessage("Log capture records"),
"logsTest": MessageLookupByLibrary.simpleMessage("Logs test"),
"loopback": MessageLookupByLibrary.simpleMessage("Loopback unlock tool"),
"loopbackDesc": MessageLookupByLibrary.simpleMessage(
"Used for UWP loopback unlocking",
@@ -379,6 +390,7 @@ class MessageLookup extends MessageLookupByLibrary {
"Modify the default system exit event",
),
"minutes": MessageLookupByLibrary.simpleMessage("Minutes"),
"mixedPort": MessageLookupByLibrary.simpleMessage("Mixed Port"),
"mode": MessageLookupByLibrary.simpleMessage("Mode"),
"monochromeScheme": MessageLookupByLibrary.simpleMessage("Monochrome"),
"months": MessageLookupByLibrary.simpleMessage("Months"),
@@ -410,29 +422,21 @@ class MessageLookup extends MessageLookupByLibrary {
"noInfo": MessageLookupByLibrary.simpleMessage("No info"),
"noMoreInfoDesc": MessageLookupByLibrary.simpleMessage("No more info"),
"noNetwork": MessageLookupByLibrary.simpleMessage("No network"),
"noNetworkApp": MessageLookupByLibrary.simpleMessage("No network App"),
"noNetworkApp": MessageLookupByLibrary.simpleMessage("No network APP"),
"noProxy": MessageLookupByLibrary.simpleMessage("No proxy"),
"noProxyDesc": MessageLookupByLibrary.simpleMessage(
"Please create a profile or add a valid profile",
),
"noResolve": MessageLookupByLibrary.simpleMessage("No resolve IP"),
"none": MessageLookupByLibrary.simpleMessage("none"),
"notEmpty": MessageLookupByLibrary.simpleMessage("Cannot be empty"),
"notSelectedTip": MessageLookupByLibrary.simpleMessage(
"The current proxy group cannot be selected.",
),
"nullConnectionsDesc": MessageLookupByLibrary.simpleMessage(
"No connections",
),
"nullCoreInfoDesc": MessageLookupByLibrary.simpleMessage(
"Unable to obtain core info",
),
"nullLogsDesc": MessageLookupByLibrary.simpleMessage("No logs"),
"nullProfileDesc": MessageLookupByLibrary.simpleMessage(
"No profile, Please add a profile",
),
"nullProxies": MessageLookupByLibrary.simpleMessage("No proxies"),
"nullRequestsDesc": MessageLookupByLibrary.simpleMessage("No requests"),
"nullTip": m4,
"numberTip": m5,
"oneColumn": MessageLookupByLibrary.simpleMessage("One column"),
"onlyIcon": MessageLookupByLibrary.simpleMessage("Icon"),
"onlyOtherApps": MessageLookupByLibrary.simpleMessage(
@@ -458,18 +462,21 @@ class MessageLookup extends MessageLookupByLibrary {
"overrideDnsDesc": MessageLookupByLibrary.simpleMessage(
"Turning it on will override the DNS options in the profile",
),
"overrideInvalidTip": MessageLookupByLibrary.simpleMessage(
"Does not take effect in script mode",
),
"overrideOriginRules": MessageLookupByLibrary.simpleMessage(
"Override the original rule",
),
"palette": MessageLookupByLibrary.simpleMessage("Palette"),
"password": MessageLookupByLibrary.simpleMessage("Password"),
"passwordTip": MessageLookupByLibrary.simpleMessage(
"Password cannot be empty",
),
"paste": MessageLookupByLibrary.simpleMessage("Paste"),
"pleaseBindWebDAV": MessageLookupByLibrary.simpleMessage(
"Please bind WebDAV",
),
"pleaseEnterScriptName": MessageLookupByLibrary.simpleMessage(
"Please enter a script name",
),
"pleaseInputAdminPassword": MessageLookupByLibrary.simpleMessage(
"Please enter the admin password",
),
@@ -480,6 +487,10 @@ class MessageLookup extends MessageLookupByLibrary {
"Please upload a valid QR code",
),
"port": MessageLookupByLibrary.simpleMessage("Port"),
"portConflictTip": MessageLookupByLibrary.simpleMessage(
"Please enter a different port",
),
"portTip": m6,
"preferH3Desc": MessageLookupByLibrary.simpleMessage(
"Prioritize the use of DOH\'s http/3",
),
@@ -538,7 +549,17 @@ class MessageLookup extends MessageLookupByLibrary {
"recoveryProfiles": MessageLookupByLibrary.simpleMessage(
"Only recovery profiles",
),
"recoveryStrategy": MessageLookupByLibrary.simpleMessage(
"Recovery strategy",
),
"recoveryStrategy_compatible": MessageLookupByLibrary.simpleMessage(
"Compatible",
),
"recoveryStrategy_override": MessageLookupByLibrary.simpleMessage(
"Override",
),
"recoverySuccess": MessageLookupByLibrary.simpleMessage("Recovery success"),
"redirPort": MessageLookupByLibrary.simpleMessage("Redir Port"),
"redo": MessageLookupByLibrary.simpleMessage("redo"),
"regExp": MessageLookupByLibrary.simpleMessage("RegExp"),
"remote": MessageLookupByLibrary.simpleMessage("Remote"),
@@ -549,6 +570,7 @@ class MessageLookup extends MessageLookupByLibrary {
"Recovery data from WebDAV",
),
"remove": MessageLookupByLibrary.simpleMessage("Remove"),
"rename": MessageLookupByLibrary.simpleMessage("Rename"),
"requests": MessageLookupByLibrary.simpleMessage("Requests"),
"requestsDesc": MessageLookupByLibrary.simpleMessage(
"View recently request records",
@@ -575,14 +597,8 @@ class MessageLookup extends MessageLookupByLibrary {
"ru": MessageLookupByLibrary.simpleMessage("Russian"),
"rule": MessageLookupByLibrary.simpleMessage("Rule"),
"ruleName": MessageLookupByLibrary.simpleMessage("Rule name"),
"ruleProviderEmptyTip": MessageLookupByLibrary.simpleMessage(
"Rule provider cannot be empty",
),
"ruleProviders": MessageLookupByLibrary.simpleMessage("Rule providers"),
"ruleTarget": MessageLookupByLibrary.simpleMessage("Rule target"),
"ruleTargetEmptyTip": MessageLookupByLibrary.simpleMessage(
"Rule target cannot be empty",
),
"save": MessageLookupByLibrary.simpleMessage("Save"),
"saveChanges": MessageLookupByLibrary.simpleMessage(
"Do you want to save the changes?",
@@ -590,11 +606,12 @@ class MessageLookup extends MessageLookupByLibrary {
"saveTip": MessageLookupByLibrary.simpleMessage(
"Are you sure you want to save?",
),
"script": MessageLookupByLibrary.simpleMessage("Script"),
"search": MessageLookupByLibrary.simpleMessage("Search"),
"seconds": MessageLookupByLibrary.simpleMessage("Seconds"),
"selectAll": MessageLookupByLibrary.simpleMessage("Select all"),
"selected": MessageLookupByLibrary.simpleMessage("Selected"),
"selectedCountTitle": m0,
"selectedCountTitle": m7,
"settings": MessageLookupByLibrary.simpleMessage("Settings"),
"show": MessageLookupByLibrary.simpleMessage("Show"),
"shrink": MessageLookupByLibrary.simpleMessage("Shrink"),
@@ -603,6 +620,7 @@ class MessageLookup extends MessageLookupByLibrary {
"Start in the background",
),
"size": MessageLookupByLibrary.simpleMessage("Size"),
"socksPort": MessageLookupByLibrary.simpleMessage("Socks Port"),
"sort": MessageLookupByLibrary.simpleMessage("Sort"),
"source": MessageLookupByLibrary.simpleMessage("Source"),
"sourceIp": MessageLookupByLibrary.simpleMessage("Source IP"),
@@ -618,9 +636,6 @@ class MessageLookup extends MessageLookupByLibrary {
"stopVpn": MessageLookupByLibrary.simpleMessage("Stopping VPN..."),
"style": MessageLookupByLibrary.simpleMessage("Style"),
"subRule": MessageLookupByLibrary.simpleMessage("Sub rule"),
"subRuleEmptyTip": MessageLookupByLibrary.simpleMessage(
"Sub rule content cannot be empty",
),
"submit": MessageLookupByLibrary.simpleMessage("Submit"),
"sync": MessageLookupByLibrary.simpleMessage("Sync"),
"system": MessageLookupByLibrary.simpleMessage("System"),
@@ -654,6 +669,7 @@ class MessageLookup extends MessageLookupByLibrary {
"toggle": MessageLookupByLibrary.simpleMessage("Toggle"),
"tonalSpotScheme": MessageLookupByLibrary.simpleMessage("TonalSpot"),
"tools": MessageLookupByLibrary.simpleMessage("Tools"),
"tproxyPort": MessageLookupByLibrary.simpleMessage("Tproxy Port"),
"trafficUsage": MessageLookupByLibrary.simpleMessage("Traffic usage"),
"tun": MessageLookupByLibrary.simpleMessage("TUN"),
"tunDesc": MessageLookupByLibrary.simpleMessage(
@@ -669,18 +685,17 @@ class MessageLookup extends MessageLookupByLibrary {
"Remove extra delays such as handshaking",
),
"unknown": MessageLookupByLibrary.simpleMessage("Unknown"),
"unnamed": MessageLookupByLibrary.simpleMessage("Unnamed"),
"update": MessageLookupByLibrary.simpleMessage("Update"),
"upload": MessageLookupByLibrary.simpleMessage("Upload"),
"url": MessageLookupByLibrary.simpleMessage("URL"),
"urlDesc": MessageLookupByLibrary.simpleMessage(
"Obtain profile through URL",
),
"urlTip": m8,
"useHosts": MessageLookupByLibrary.simpleMessage("Use hosts"),
"useSystemHosts": MessageLookupByLibrary.simpleMessage("Use system hosts"),
"value": MessageLookupByLibrary.simpleMessage("Value"),
"valueExists": MessageLookupByLibrary.simpleMessage(
"The current value already exists",
),
"vibrantScheme": MessageLookupByLibrary.simpleMessage("Vibrant"),
"view": MessageLookupByLibrary.simpleMessage("View"),
"vpnDesc": MessageLookupByLibrary.simpleMessage(

View File

@@ -20,7 +20,23 @@ typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
class MessageLookup extends MessageLookupByLibrary {
String get localeName => 'ja';
static String m0(count) => "${count} 項目が選択されています";
static String m0(label) => "選択された${label}を削除してもよろしいですか?";
static String m1(label) => "現在の${label}を削除してもよろしいですか?";
static String m2(label) => "${label}は空欄にできません";
static String m3(label) => "現在の${label}は既に存在しています";
static String m4(label) => "現在${label}はありません";
static String m5(label) => "${label}は数字でなければなりません";
static String m6(label) => "${label} は 1024 から 49151 の間でなければなりません";
static String m7(count) => "${count} 項目が選択されています";
static String m8(label) => "${label}はURLである必要があります";
final messages = _notInlinedMessages(_notInlinedMessages);
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
@@ -36,7 +52,6 @@ class MessageLookup extends MessageLookupByLibrary {
"選択したアプリをVPNから除外",
),
"account": MessageLookupByLibrary.simpleMessage("アカウント"),
"accountTip": MessageLookupByLibrary.simpleMessage("アカウントは必須です"),
"action": MessageLookupByLibrary.simpleMessage("アクション"),
"action_mode": MessageLookupByLibrary.simpleMessage("モード切替"),
"action_proxy": MessageLookupByLibrary.simpleMessage("システムプロキシ"),
@@ -78,6 +93,7 @@ class MessageLookup extends MessageLookupByLibrary {
"autoLaunchDesc": MessageLookupByLibrary.simpleMessage("システムの自動起動に従う"),
"autoRun": MessageLookupByLibrary.simpleMessage("自動実行"),
"autoRunDesc": MessageLookupByLibrary.simpleMessage("アプリ起動時に自動実行"),
"autoSetSystemDns": MessageLookupByLibrary.simpleMessage("オートセットシステムDNS"),
"autoUpdate": MessageLookupByLibrary.simpleMessage("自動更新"),
"autoUpdateInterval": MessageLookupByLibrary.simpleMessage("自動更新間隔(分)"),
"backup": MessageLookupByLibrary.simpleMessage("バックアップ"),
@@ -107,7 +123,7 @@ class MessageLookup extends MessageLookupByLibrary {
"clearData": MessageLookupByLibrary.simpleMessage("データを消去"),
"clipboardExport": MessageLookupByLibrary.simpleMessage("クリップボードにエクスポート"),
"clipboardImport": MessageLookupByLibrary.simpleMessage("クリップボードからインポート"),
"colorExists": MessageLookupByLibrary.simpleMessage("この色は既に存在します"),
"color": MessageLookupByLibrary.simpleMessage("カラー"),
"colorSchemes": MessageLookupByLibrary.simpleMessage("カラースキーム"),
"columns": MessageLookupByLibrary.simpleMessage(""),
"compatible": MessageLookupByLibrary.simpleMessage("互換モード"),
@@ -118,8 +134,8 @@ class MessageLookup extends MessageLookupByLibrary {
"connections": MessageLookupByLibrary.simpleMessage("接続"),
"connectionsDesc": MessageLookupByLibrary.simpleMessage("現在の接続データを表示"),
"connectivity": MessageLookupByLibrary.simpleMessage("接続性:"),
"contactMe": MessageLookupByLibrary.simpleMessage("連絡する"),
"content": MessageLookupByLibrary.simpleMessage("内容"),
"contentEmptyTip": MessageLookupByLibrary.simpleMessage("内容は必須です"),
"contentScheme": MessageLookupByLibrary.simpleMessage("コンテンツテーマ"),
"copy": MessageLookupByLibrary.simpleMessage("コピー"),
"copyEnvVar": MessageLookupByLibrary.simpleMessage("環境変数をコピー"),
@@ -143,11 +159,8 @@ class MessageLookup extends MessageLookupByLibrary {
"delay": MessageLookupByLibrary.simpleMessage("遅延"),
"delaySort": MessageLookupByLibrary.simpleMessage("遅延順"),
"delete": MessageLookupByLibrary.simpleMessage("削除"),
"deleteColorTip": MessageLookupByLibrary.simpleMessage("現在の色を削除しますか?"),
"deleteProfileTip": MessageLookupByLibrary.simpleMessage(
"現在のプロファイルを削除しますか?",
),
"deleteRuleTip": MessageLookupByLibrary.simpleMessage("選択したルールを削除しますか?"),
"deleteMultipTip": m0,
"deleteTip": m1,
"desc": MessageLookupByLibrary.simpleMessage(
"ClashMetaベースのマルチプラットフォームプロキシクライアント。シンプルで使いやすく、オープンソースで広告なし。",
),
@@ -169,6 +182,7 @@ class MessageLookup extends MessageLookupByLibrary {
"domain": MessageLookupByLibrary.simpleMessage("ドメイン"),
"download": MessageLookupByLibrary.simpleMessage("ダウンロード"),
"edit": MessageLookupByLibrary.simpleMessage("編集"),
"emptyTip": m2,
"en": MessageLookupByLibrary.simpleMessage("英語"),
"enableOverride": MessageLookupByLibrary.simpleMessage("上書きを有効化"),
"entries": MessageLookupByLibrary.simpleMessage(" エントリ"),
@@ -176,6 +190,7 @@ class MessageLookup extends MessageLookupByLibrary {
"excludeDesc": MessageLookupByLibrary.simpleMessage(
"アプリがバックグラウンド時に最近のタスクから非表示",
),
"existsTip": m3,
"exit": MessageLookupByLibrary.simpleMessage("終了"),
"expand": MessageLookupByLibrary.simpleMessage("標準"),
"expirationTime": MessageLookupByLibrary.simpleMessage("有効期限"),
@@ -231,12 +246,16 @@ class MessageLookup extends MessageLookupByLibrary {
"icon": MessageLookupByLibrary.simpleMessage("アイコン"),
"iconConfiguration": MessageLookupByLibrary.simpleMessage("アイコン設定"),
"iconStyle": MessageLookupByLibrary.simpleMessage("アイコンスタイル"),
"import": MessageLookupByLibrary.simpleMessage("インポート"),
"importFile": MessageLookupByLibrary.simpleMessage("ファイルからインポート"),
"importFromURL": MessageLookupByLibrary.simpleMessage("URLからインポート"),
"importUrl": MessageLookupByLibrary.simpleMessage("URLからインポート"),
"infiniteTime": MessageLookupByLibrary.simpleMessage("長期有効"),
"init": MessageLookupByLibrary.simpleMessage("初期化"),
"inputCorrectHotkey": MessageLookupByLibrary.simpleMessage("正しいホットキーを入力"),
"intelligentSelected": MessageLookupByLibrary.simpleMessage("インテリジェント選択"),
"internet": MessageLookupByLibrary.simpleMessage("インターネット"),
"interval": MessageLookupByLibrary.simpleMessage("インターバル"),
"intranetIP": MessageLookupByLibrary.simpleMessage("イントラネットIP"),
"ipcidr": MessageLookupByLibrary.simpleMessage("IPCIDR"),
"ipv6Desc": MessageLookupByLibrary.simpleMessage("有効化するとIPv6トラフィックを受信可能"),
@@ -247,7 +266,6 @@ class MessageLookup extends MessageLookupByLibrary {
"TCPキープアライブ間隔",
),
"key": MessageLookupByLibrary.simpleMessage("キー"),
"keyExists": MessageLookupByLibrary.simpleMessage("現在のキーは既に存在します"),
"language": MessageLookupByLibrary.simpleMessage("言語"),
"layout": MessageLookupByLibrary.simpleMessage("レイアウト"),
"light": MessageLookupByLibrary.simpleMessage("ライト"),
@@ -261,6 +279,7 @@ class MessageLookup extends MessageLookupByLibrary {
"logcatDesc": MessageLookupByLibrary.simpleMessage("無効化するとログエントリを非表示"),
"logs": MessageLookupByLibrary.simpleMessage("ログ"),
"logsDesc": MessageLookupByLibrary.simpleMessage("ログキャプチャ記録"),
"logsTest": MessageLookupByLibrary.simpleMessage("ログテスト"),
"loopback": MessageLookupByLibrary.simpleMessage("ループバック解除ツール"),
"loopbackDesc": MessageLookupByLibrary.simpleMessage("UWPループバック解除用"),
"loose": MessageLookupByLibrary.simpleMessage(""),
@@ -273,6 +292,7 @@ class MessageLookup extends MessageLookupByLibrary {
"システムの終了イベントを変更",
),
"minutes": MessageLookupByLibrary.simpleMessage(""),
"mixedPort": MessageLookupByLibrary.simpleMessage("混合ポート"),
"mode": MessageLookupByLibrary.simpleMessage("モード"),
"monochromeScheme": MessageLookupByLibrary.simpleMessage("モノクローム"),
"months": MessageLookupByLibrary.simpleMessage(""),
@@ -303,18 +323,14 @@ class MessageLookup extends MessageLookupByLibrary {
),
"noResolve": MessageLookupByLibrary.simpleMessage("IPを解決しない"),
"none": MessageLookupByLibrary.simpleMessage("なし"),
"notEmpty": MessageLookupByLibrary.simpleMessage("空欄不可"),
"notSelectedTip": MessageLookupByLibrary.simpleMessage(
"現在のプロキシグループは選択できません",
),
"nullConnectionsDesc": MessageLookupByLibrary.simpleMessage("接続なし"),
"nullCoreInfoDesc": MessageLookupByLibrary.simpleMessage("コア情報を取得できません"),
"nullLogsDesc": MessageLookupByLibrary.simpleMessage("ログがありません"),
"nullProfileDesc": MessageLookupByLibrary.simpleMessage(
"プロファイルがありません。追加してください",
),
"nullProxies": MessageLookupByLibrary.simpleMessage("プロキシなし"),
"nullRequestsDesc": MessageLookupByLibrary.simpleMessage("リクエストなし"),
"nullTip": m4,
"numberTip": m5,
"oneColumn": MessageLookupByLibrary.simpleMessage("1列"),
"onlyIcon": MessageLookupByLibrary.simpleMessage("アイコンのみ"),
"onlyOtherApps": MessageLookupByLibrary.simpleMessage("サードパーティアプリのみ"),
@@ -332,14 +348,19 @@ class MessageLookup extends MessageLookupByLibrary {
"overrideDnsDesc": MessageLookupByLibrary.simpleMessage(
"有効化するとプロファイルのDNS設定を上書き",
),
"overrideInvalidTip": MessageLookupByLibrary.simpleMessage(
"スクリプトモードでは有効になりません",
),
"overrideOriginRules": MessageLookupByLibrary.simpleMessage("元のルールを上書き"),
"palette": MessageLookupByLibrary.simpleMessage("パレット"),
"password": MessageLookupByLibrary.simpleMessage("パスワード"),
"passwordTip": MessageLookupByLibrary.simpleMessage("パスワードは必須です"),
"paste": MessageLookupByLibrary.simpleMessage("貼り付け"),
"pleaseBindWebDAV": MessageLookupByLibrary.simpleMessage(
"WebDAVをバインドしてください",
),
"pleaseEnterScriptName": MessageLookupByLibrary.simpleMessage(
"スクリプト名を入力してください",
),
"pleaseInputAdminPassword": MessageLookupByLibrary.simpleMessage(
"管理者パスワードを入力",
),
@@ -350,6 +371,8 @@ class MessageLookup extends MessageLookupByLibrary {
"有効なQRコードをアップロードしてください",
),
"port": MessageLookupByLibrary.simpleMessage("ポート"),
"portConflictTip": MessageLookupByLibrary.simpleMessage("別のポートを入力してください"),
"portTip": m6,
"preferH3Desc": MessageLookupByLibrary.simpleMessage("DOHのHTTP/3を優先使用"),
"pressKeyboard": MessageLookupByLibrary.simpleMessage("キーボードを押してください"),
"preview": MessageLookupByLibrary.simpleMessage("プレビュー"),
@@ -394,7 +417,13 @@ class MessageLookup extends MessageLookupByLibrary {
"recovery": MessageLookupByLibrary.simpleMessage("復元"),
"recoveryAll": MessageLookupByLibrary.simpleMessage("全データ復元"),
"recoveryProfiles": MessageLookupByLibrary.simpleMessage("プロファイルのみ復元"),
"recoveryStrategy": MessageLookupByLibrary.simpleMessage("リカバリー戦略"),
"recoveryStrategy_compatible": MessageLookupByLibrary.simpleMessage("互換性"),
"recoveryStrategy_override": MessageLookupByLibrary.simpleMessage(
"オーバーライド",
),
"recoverySuccess": MessageLookupByLibrary.simpleMessage("復元成功"),
"redirPort": MessageLookupByLibrary.simpleMessage("Redirポート"),
"redo": MessageLookupByLibrary.simpleMessage("やり直す"),
"regExp": MessageLookupByLibrary.simpleMessage("正規表現"),
"remote": MessageLookupByLibrary.simpleMessage("リモート"),
@@ -405,6 +434,7 @@ class MessageLookup extends MessageLookupByLibrary {
"WebDAVからデータを復元",
),
"remove": MessageLookupByLibrary.simpleMessage("削除"),
"rename": MessageLookupByLibrary.simpleMessage("リネーム"),
"requests": MessageLookupByLibrary.simpleMessage("リクエスト"),
"requestsDesc": MessageLookupByLibrary.simpleMessage("最近のリクエスト記録を表示"),
"reset": MessageLookupByLibrary.simpleMessage("リセット"),
@@ -425,26 +455,24 @@ class MessageLookup extends MessageLookupByLibrary {
"ru": MessageLookupByLibrary.simpleMessage("ロシア語"),
"rule": MessageLookupByLibrary.simpleMessage("ルール"),
"ruleName": MessageLookupByLibrary.simpleMessage("ルール名"),
"ruleProviderEmptyTip": MessageLookupByLibrary.simpleMessage(
"ルールプロバイダーは必須です",
),
"ruleProviders": MessageLookupByLibrary.simpleMessage("ルールプロバイダー"),
"ruleTarget": MessageLookupByLibrary.simpleMessage("ルール対象"),
"ruleTargetEmptyTip": MessageLookupByLibrary.simpleMessage("ルール対象は必須です"),
"save": MessageLookupByLibrary.simpleMessage("保存"),
"saveChanges": MessageLookupByLibrary.simpleMessage("変更を保存しますか?"),
"saveTip": MessageLookupByLibrary.simpleMessage("保存してもよろしいですか?"),
"script": MessageLookupByLibrary.simpleMessage("スクリプト"),
"search": MessageLookupByLibrary.simpleMessage("検索"),
"seconds": MessageLookupByLibrary.simpleMessage(""),
"selectAll": MessageLookupByLibrary.simpleMessage("すべて選択"),
"selected": MessageLookupByLibrary.simpleMessage("選択済み"),
"selectedCountTitle": m0,
"selectedCountTitle": m7,
"settings": MessageLookupByLibrary.simpleMessage("設定"),
"show": MessageLookupByLibrary.simpleMessage("表示"),
"shrink": MessageLookupByLibrary.simpleMessage("縮小"),
"silentLaunch": MessageLookupByLibrary.simpleMessage("バックグラウンド起動"),
"silentLaunchDesc": MessageLookupByLibrary.simpleMessage("バックグラウンドで起動"),
"size": MessageLookupByLibrary.simpleMessage("サイズ"),
"socksPort": MessageLookupByLibrary.simpleMessage("Socksポート"),
"sort": MessageLookupByLibrary.simpleMessage("並び替え"),
"source": MessageLookupByLibrary.simpleMessage("ソース"),
"sourceIp": MessageLookupByLibrary.simpleMessage("送信元IP"),
@@ -458,7 +486,6 @@ class MessageLookup extends MessageLookupByLibrary {
"stopVpn": MessageLookupByLibrary.simpleMessage("VPNを停止中..."),
"style": MessageLookupByLibrary.simpleMessage("スタイル"),
"subRule": MessageLookupByLibrary.simpleMessage("サブルール"),
"subRuleEmptyTip": MessageLookupByLibrary.simpleMessage("サブルールの内容は必須です"),
"submit": MessageLookupByLibrary.simpleMessage("送信"),
"sync": MessageLookupByLibrary.simpleMessage("同期"),
"system": MessageLookupByLibrary.simpleMessage("システム"),
@@ -486,6 +513,7 @@ class MessageLookup extends MessageLookupByLibrary {
"toggle": MessageLookupByLibrary.simpleMessage("トグル"),
"tonalSpotScheme": MessageLookupByLibrary.simpleMessage("トーンスポット"),
"tools": MessageLookupByLibrary.simpleMessage("ツール"),
"tproxyPort": MessageLookupByLibrary.simpleMessage("Tproxyポート"),
"trafficUsage": MessageLookupByLibrary.simpleMessage("トラフィック使用量"),
"tun": MessageLookupByLibrary.simpleMessage("TUN"),
"tunDesc": MessageLookupByLibrary.simpleMessage("管理者モードでのみ有効"),
@@ -499,14 +527,15 @@ class MessageLookup extends MessageLookupByLibrary {
"ハンドシェイクなどの余分な遅延を削除",
),
"unknown": MessageLookupByLibrary.simpleMessage("不明"),
"unnamed": MessageLookupByLibrary.simpleMessage("無題"),
"update": MessageLookupByLibrary.simpleMessage("更新"),
"upload": MessageLookupByLibrary.simpleMessage("アップロード"),
"url": MessageLookupByLibrary.simpleMessage("URL"),
"urlDesc": MessageLookupByLibrary.simpleMessage("URL経由でプロファイルを取得"),
"urlTip": m8,
"useHosts": MessageLookupByLibrary.simpleMessage("ホストを使用"),
"useSystemHosts": MessageLookupByLibrary.simpleMessage("システムホストを使用"),
"value": MessageLookupByLibrary.simpleMessage(""),
"valueExists": MessageLookupByLibrary.simpleMessage("現在の値は既に存在します"),
"vibrantScheme": MessageLookupByLibrary.simpleMessage("ビブラント"),
"view": MessageLookupByLibrary.simpleMessage("表示"),
"vpnDesc": MessageLookupByLibrary.simpleMessage("VPN関連設定の変更"),

View File

@@ -20,7 +20,24 @@ typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
class MessageLookup extends MessageLookupByLibrary {
String get localeName => 'ru';
static String m0(count) => "Выбрано ${count} элементов";
static String m0(label) =>
"Вы уверены, что хотите удалить выбранные ${label}?";
static String m1(label) => "Вы уверены, что хотите удалить текущий ${label}?";
static String m2(label) => "${label} не может быть пустым";
static String m3(label) => "Текущий ${label} уже существует";
static String m4(label) => "Сейчас ${label} нет";
static String m5(label) => "${label} должно быть числом";
static String m6(label) => "${label} должен быть числом от 1024 до 49151";
static String m7(count) => "Выбрано ${count} элементов";
static String m8(label) => "${label} должен быть URL";
final messages = _notInlinedMessages(_notInlinedMessages);
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
@@ -36,9 +53,6 @@ class MessageLookup extends MessageLookupByLibrary {
"Выбранные приложения будут исключены из VPN",
),
"account": MessageLookupByLibrary.simpleMessage("Аккаунт"),
"accountTip": MessageLookupByLibrary.simpleMessage(
"Аккаунт не может быть пустым",
),
"action": MessageLookupByLibrary.simpleMessage("Действие"),
"action_mode": MessageLookupByLibrary.simpleMessage("Переключить режим"),
"action_proxy": MessageLookupByLibrary.simpleMessage("Системный прокси"),
@@ -106,6 +120,9 @@ class MessageLookup extends MessageLookupByLibrary {
"autoRunDesc": MessageLookupByLibrary.simpleMessage(
"Автоматический запуск при открытии приложения",
),
"autoSetSystemDns": MessageLookupByLibrary.simpleMessage(
"Автоматическая настройка системного DNS",
),
"autoUpdate": MessageLookupByLibrary.simpleMessage("Автообновление"),
"autoUpdateInterval": MessageLookupByLibrary.simpleMessage(
"Интервал автообновления (минуты)",
@@ -155,9 +172,7 @@ class MessageLookup extends MessageLookupByLibrary {
"clipboardImport": MessageLookupByLibrary.simpleMessage(
"Импорт из буфера обмена",
),
"colorExists": MessageLookupByLibrary.simpleMessage(
"Этот цвет уже существует",
),
"color": MessageLookupByLibrary.simpleMessage("Цвет"),
"colorSchemes": MessageLookupByLibrary.simpleMessage("Цветовые схемы"),
"columns": MessageLookupByLibrary.simpleMessage("Столбцы"),
"compatible": MessageLookupByLibrary.simpleMessage("Режим совместимости"),
@@ -170,10 +185,8 @@ class MessageLookup extends MessageLookupByLibrary {
"Просмотр текущих данных о соединениях",
),
"connectivity": MessageLookupByLibrary.simpleMessage("Связь:"),
"contactMe": MessageLookupByLibrary.simpleMessage("Свяжитесь со мной"),
"content": MessageLookupByLibrary.simpleMessage("Содержание"),
"contentEmptyTip": MessageLookupByLibrary.simpleMessage(
"Содержание не может быть пустым",
),
"contentScheme": MessageLookupByLibrary.simpleMessage("Контентная тема"),
"copy": MessageLookupByLibrary.simpleMessage("Копировать"),
"copyEnvVar": MessageLookupByLibrary.simpleMessage(
@@ -203,15 +216,8 @@ class MessageLookup extends MessageLookupByLibrary {
"delay": MessageLookupByLibrary.simpleMessage("Задержка"),
"delaySort": MessageLookupByLibrary.simpleMessage("Сортировка по задержке"),
"delete": MessageLookupByLibrary.simpleMessage("Удалить"),
"deleteColorTip": MessageLookupByLibrary.simpleMessage(
"Удалить текущий цвет?",
),
"deleteProfileTip": MessageLookupByLibrary.simpleMessage(
"Вы уверены, что хотите удалить текущий профиль?",
),
"deleteRuleTip": MessageLookupByLibrary.simpleMessage(
"Вы уверены, что хотите удалить выбранное правило?",
),
"deleteMultipTip": m0,
"deleteTip": m1,
"desc": MessageLookupByLibrary.simpleMessage(
"Многоплатформенный прокси-клиент на основе ClashMeta, простой и удобный в использовании, с открытым исходным кодом и без рекламы.",
),
@@ -245,6 +251,7 @@ class MessageLookup extends MessageLookupByLibrary {
"domain": MessageLookupByLibrary.simpleMessage("Домен"),
"download": MessageLookupByLibrary.simpleMessage("Скачивание"),
"edit": MessageLookupByLibrary.simpleMessage("Редактировать"),
"emptyTip": m2,
"en": MessageLookupByLibrary.simpleMessage("Английский"),
"enableOverride": MessageLookupByLibrary.simpleMessage(
"Включить переопределение",
@@ -256,6 +263,7 @@ class MessageLookup extends MessageLookupByLibrary {
"excludeDesc": MessageLookupByLibrary.simpleMessage(
"Когда приложение находится в фоновом режиме, оно скрыто из последних задач",
),
"existsTip": m3,
"exit": MessageLookupByLibrary.simpleMessage("Выход"),
"expand": MessageLookupByLibrary.simpleMessage("Стандартный"),
"expirationTime": MessageLookupByLibrary.simpleMessage("Время истечения"),
@@ -337,7 +345,10 @@ class MessageLookup extends MessageLookupByLibrary {
"Конфигурация иконки",
),
"iconStyle": MessageLookupByLibrary.simpleMessage("Стиль иконки"),
"import": MessageLookupByLibrary.simpleMessage("Импорт"),
"importFile": MessageLookupByLibrary.simpleMessage("Импорт из файла"),
"importFromURL": MessageLookupByLibrary.simpleMessage("Импорт из URL"),
"importUrl": MessageLookupByLibrary.simpleMessage("Импорт по URL"),
"infiniteTime": MessageLookupByLibrary.simpleMessage(
"Долгосрочное действие",
),
@@ -349,6 +360,7 @@ class MessageLookup extends MessageLookupByLibrary {
"Интеллектуальный выбор",
),
"internet": MessageLookupByLibrary.simpleMessage("Интернет"),
"interval": MessageLookupByLibrary.simpleMessage("Интервал"),
"intranetIP": MessageLookupByLibrary.simpleMessage("Внутренний IP"),
"ipcidr": MessageLookupByLibrary.simpleMessage("IPCIDR"),
"ipv6Desc": MessageLookupByLibrary.simpleMessage(
@@ -363,9 +375,6 @@ class MessageLookup extends MessageLookupByLibrary {
"Интервал поддержания TCP-соединения",
),
"key": MessageLookupByLibrary.simpleMessage("Ключ"),
"keyExists": MessageLookupByLibrary.simpleMessage(
"Текущий ключ уже существует",
),
"language": MessageLookupByLibrary.simpleMessage("Язык"),
"layout": MessageLookupByLibrary.simpleMessage("Макет"),
"light": MessageLookupByLibrary.simpleMessage("Светлый"),
@@ -385,6 +394,7 @@ class MessageLookup extends MessageLookupByLibrary {
),
"logs": MessageLookupByLibrary.simpleMessage("Логи"),
"logsDesc": MessageLookupByLibrary.simpleMessage("Записи захвата логов"),
"logsTest": MessageLookupByLibrary.simpleMessage("Тест журналов"),
"loopback": MessageLookupByLibrary.simpleMessage(
"Инструмент разблокировки Loopback",
),
@@ -405,6 +415,7 @@ class MessageLookup extends MessageLookupByLibrary {
"Изменить стандартное событие выхода из системы",
),
"minutes": MessageLookupByLibrary.simpleMessage("Минут"),
"mixedPort": MessageLookupByLibrary.simpleMessage("Смешанный порт"),
"mode": MessageLookupByLibrary.simpleMessage("Режим"),
"monochromeScheme": MessageLookupByLibrary.simpleMessage("Монохром"),
"months": MessageLookupByLibrary.simpleMessage("Месяцев"),
@@ -445,22 +456,14 @@ class MessageLookup extends MessageLookupByLibrary {
),
"noResolve": MessageLookupByLibrary.simpleMessage("Не разрешать IP"),
"none": MessageLookupByLibrary.simpleMessage("Нет"),
"notEmpty": MessageLookupByLibrary.simpleMessage("Не может быть пустым"),
"notSelectedTip": MessageLookupByLibrary.simpleMessage(
"Текущая группа прокси не может быть выбрана.",
),
"nullConnectionsDesc": MessageLookupByLibrary.simpleMessage(
"Нет соединений",
),
"nullCoreInfoDesc": MessageLookupByLibrary.simpleMessage(
"Не удалось получить информацию о ядре",
),
"nullLogsDesc": MessageLookupByLibrary.simpleMessage("Нет логов"),
"nullProfileDesc": MessageLookupByLibrary.simpleMessage(
"Нет профиля, пожалуйста, добавьте профиль",
),
"nullProxies": MessageLookupByLibrary.simpleMessage("Нет прокси"),
"nullRequestsDesc": MessageLookupByLibrary.simpleMessage("Нет запросов"),
"nullTip": m4,
"numberTip": m5,
"oneColumn": MessageLookupByLibrary.simpleMessage("Один столбец"),
"onlyIcon": MessageLookupByLibrary.simpleMessage("Только иконка"),
"onlyOtherApps": MessageLookupByLibrary.simpleMessage(
@@ -488,18 +491,21 @@ class MessageLookup extends MessageLookupByLibrary {
"overrideDnsDesc": MessageLookupByLibrary.simpleMessage(
"Включение переопределит настройки DNS в профиле",
),
"overrideInvalidTip": MessageLookupByLibrary.simpleMessage(
"В скриптовом режиме не действует",
),
"overrideOriginRules": MessageLookupByLibrary.simpleMessage(
"Переопределить оригинальное правило",
),
"palette": MessageLookupByLibrary.simpleMessage("Палитра"),
"password": MessageLookupByLibrary.simpleMessage("Пароль"),
"passwordTip": MessageLookupByLibrary.simpleMessage(
"Пароль не может быть пустым",
),
"paste": MessageLookupByLibrary.simpleMessage("Вставить"),
"pleaseBindWebDAV": MessageLookupByLibrary.simpleMessage(
"Пожалуйста, привяжите WebDAV",
),
"pleaseEnterScriptName": MessageLookupByLibrary.simpleMessage(
"Пожалуйста, введите название скрипта",
),
"pleaseInputAdminPassword": MessageLookupByLibrary.simpleMessage(
"Пожалуйста, введите пароль администратора",
),
@@ -510,6 +516,10 @@ class MessageLookup extends MessageLookupByLibrary {
"Пожалуйста, загрузите действительный QR-код",
),
"port": MessageLookupByLibrary.simpleMessage("Порт"),
"portConflictTip": MessageLookupByLibrary.simpleMessage(
"Введите другой порт",
),
"portTip": m6,
"preferH3Desc": MessageLookupByLibrary.simpleMessage(
"Приоритетное использование HTTP/3 для DOH",
),
@@ -572,9 +582,19 @@ class MessageLookup extends MessageLookupByLibrary {
"recoveryProfiles": MessageLookupByLibrary.simpleMessage(
"Только восстановление профилей",
),
"recoveryStrategy": MessageLookupByLibrary.simpleMessage(
"Стратегия восстановления",
),
"recoveryStrategy_compatible": MessageLookupByLibrary.simpleMessage(
"Совместимый",
),
"recoveryStrategy_override": MessageLookupByLibrary.simpleMessage(
"Переопределение",
),
"recoverySuccess": MessageLookupByLibrary.simpleMessage(
"Восстановление успешно",
),
"redirPort": MessageLookupByLibrary.simpleMessage("Redir-порт"),
"redo": MessageLookupByLibrary.simpleMessage("Повторить"),
"regExp": MessageLookupByLibrary.simpleMessage("Регулярное выражение"),
"remote": MessageLookupByLibrary.simpleMessage("Удаленный"),
@@ -585,6 +605,7 @@ class MessageLookup extends MessageLookupByLibrary {
"Восстановление данных с WebDAV",
),
"remove": MessageLookupByLibrary.simpleMessage("Удалить"),
"rename": MessageLookupByLibrary.simpleMessage("Переименовать"),
"requests": MessageLookupByLibrary.simpleMessage("Запросы"),
"requestsDesc": MessageLookupByLibrary.simpleMessage(
"Просмотр последних записей запросов",
@@ -615,24 +636,19 @@ class MessageLookup extends MessageLookupByLibrary {
"ru": MessageLookupByLibrary.simpleMessage("Русский"),
"rule": MessageLookupByLibrary.simpleMessage("Правило"),
"ruleName": MessageLookupByLibrary.simpleMessage("Название правила"),
"ruleProviderEmptyTip": MessageLookupByLibrary.simpleMessage(
"Поставщик правил не может быть пустым",
),
"ruleProviders": MessageLookupByLibrary.simpleMessage("Провайдеры правил"),
"ruleTarget": MessageLookupByLibrary.simpleMessage("Цель правила"),
"ruleTargetEmptyTip": MessageLookupByLibrary.simpleMessage(
"Цель правила не может быть пустой",
),
"save": MessageLookupByLibrary.simpleMessage("Сохранить"),
"saveChanges": MessageLookupByLibrary.simpleMessage("Сохранить изменения?"),
"saveTip": MessageLookupByLibrary.simpleMessage(
"Вы уверены, что хотите сохранить?",
),
"script": MessageLookupByLibrary.simpleMessage("Скрипт"),
"search": MessageLookupByLibrary.simpleMessage("Поиск"),
"seconds": MessageLookupByLibrary.simpleMessage("Секунд"),
"selectAll": MessageLookupByLibrary.simpleMessage("Выбрать все"),
"selected": MessageLookupByLibrary.simpleMessage("Выбрано"),
"selectedCountTitle": m0,
"selectedCountTitle": m7,
"settings": MessageLookupByLibrary.simpleMessage("Настройки"),
"show": MessageLookupByLibrary.simpleMessage("Показать"),
"shrink": MessageLookupByLibrary.simpleMessage("Сжать"),
@@ -641,6 +657,7 @@ class MessageLookup extends MessageLookupByLibrary {
"Запуск в фоновом режиме",
),
"size": MessageLookupByLibrary.simpleMessage("Размер"),
"socksPort": MessageLookupByLibrary.simpleMessage("Socks-порт"),
"sort": MessageLookupByLibrary.simpleMessage("Сортировка"),
"source": MessageLookupByLibrary.simpleMessage("Источник"),
"sourceIp": MessageLookupByLibrary.simpleMessage("Исходный IP"),
@@ -656,9 +673,6 @@ class MessageLookup extends MessageLookupByLibrary {
"stopVpn": MessageLookupByLibrary.simpleMessage("Остановка VPN..."),
"style": MessageLookupByLibrary.simpleMessage("Стиль"),
"subRule": MessageLookupByLibrary.simpleMessage("Подправило"),
"subRuleEmptyTip": MessageLookupByLibrary.simpleMessage(
"Содержание подправила не может быть пустым",
),
"submit": MessageLookupByLibrary.simpleMessage("Отправить"),
"sync": MessageLookupByLibrary.simpleMessage("Синхронизация"),
"system": MessageLookupByLibrary.simpleMessage("Система"),
@@ -692,6 +706,7 @@ class MessageLookup extends MessageLookupByLibrary {
"toggle": MessageLookupByLibrary.simpleMessage("Переключить"),
"tonalSpotScheme": MessageLookupByLibrary.simpleMessage("Тональный акцент"),
"tools": MessageLookupByLibrary.simpleMessage("Инструменты"),
"tproxyPort": MessageLookupByLibrary.simpleMessage("Tproxy-порт"),
"trafficUsage": MessageLookupByLibrary.simpleMessage(
"Использование трафика",
),
@@ -711,20 +726,19 @@ class MessageLookup extends MessageLookupByLibrary {
"Убрать дополнительные задержки, такие как рукопожатие",
),
"unknown": MessageLookupByLibrary.simpleMessage("Неизвестно"),
"unnamed": MessageLookupByLibrary.simpleMessage("Без имени"),
"update": MessageLookupByLibrary.simpleMessage("Обновить"),
"upload": MessageLookupByLibrary.simpleMessage("Загрузка"),
"url": MessageLookupByLibrary.simpleMessage("URL"),
"urlDesc": MessageLookupByLibrary.simpleMessage(
"Получить профиль через URL",
),
"urlTip": m8,
"useHosts": MessageLookupByLibrary.simpleMessage("Использовать hosts"),
"useSystemHosts": MessageLookupByLibrary.simpleMessage(
"Использовать системные hosts",
),
"value": MessageLookupByLibrary.simpleMessage("Значение"),
"valueExists": MessageLookupByLibrary.simpleMessage(
"Текущее значение уже существует",
),
"vibrantScheme": MessageLookupByLibrary.simpleMessage("Яркие"),
"view": MessageLookupByLibrary.simpleMessage("Просмотр"),
"vpnDesc": MessageLookupByLibrary.simpleMessage(

View File

@@ -20,7 +20,23 @@ typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
class MessageLookup extends MessageLookupByLibrary {
String get localeName => 'zh_CN';
static String m0(count) => "已选择 ${count}";
static String m0(label) => "确定删除选中的${label}吗?";
static String m1(label) => "确定删除当前${label}吗?";
static String m2(label) => "${label}不能为空";
static String m3(label) => "${label}当前已存在";
static String m4(label) => "暂无${label}";
static String m5(label) => "${label}必须为数字";
static String m6(label) => "${label} 必须在 1024 到 49151 之间";
static String m7(count) => "已选择 ${count}";
static String m8(label) => "${label}必须为URL";
final messages = _notInlinedMessages(_notInlinedMessages);
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
@@ -34,7 +50,6 @@ class MessageLookup extends MessageLookupByLibrary {
"选中应用将会被排除在VPN之外",
),
"account": MessageLookupByLibrary.simpleMessage("账号"),
"accountTip": MessageLookupByLibrary.simpleMessage("账号不能为空"),
"action": MessageLookupByLibrary.simpleMessage("操作"),
"action_mode": MessageLookupByLibrary.simpleMessage("切换模式"),
"action_proxy": MessageLookupByLibrary.simpleMessage("系统代理"),
@@ -72,6 +87,7 @@ class MessageLookup extends MessageLookupByLibrary {
"autoLaunchDesc": MessageLookupByLibrary.simpleMessage("跟随系统自启动"),
"autoRun": MessageLookupByLibrary.simpleMessage("自动运行"),
"autoRunDesc": MessageLookupByLibrary.simpleMessage("应用打开时自动运行"),
"autoSetSystemDns": MessageLookupByLibrary.simpleMessage("自动设置系统DNS"),
"autoUpdate": MessageLookupByLibrary.simpleMessage("自动更新"),
"autoUpdateInterval": MessageLookupByLibrary.simpleMessage("自动更新间隔(分钟)"),
"backup": MessageLookupByLibrary.simpleMessage("备份"),
@@ -97,7 +113,7 @@ class MessageLookup extends MessageLookupByLibrary {
"clearData": MessageLookupByLibrary.simpleMessage("清除数据"),
"clipboardExport": MessageLookupByLibrary.simpleMessage("导出剪贴板"),
"clipboardImport": MessageLookupByLibrary.simpleMessage("剪贴板导入"),
"colorExists": MessageLookupByLibrary.simpleMessage("颜色已存在"),
"color": MessageLookupByLibrary.simpleMessage("颜色"),
"colorSchemes": MessageLookupByLibrary.simpleMessage("配色方案"),
"columns": MessageLookupByLibrary.simpleMessage("列数"),
"compatible": MessageLookupByLibrary.simpleMessage("兼容模式"),
@@ -108,8 +124,8 @@ class MessageLookup extends MessageLookupByLibrary {
"connections": MessageLookupByLibrary.simpleMessage("连接"),
"connectionsDesc": MessageLookupByLibrary.simpleMessage("查看当前连接数据"),
"connectivity": MessageLookupByLibrary.simpleMessage("连通性:"),
"contactMe": MessageLookupByLibrary.simpleMessage("联系我"),
"content": MessageLookupByLibrary.simpleMessage("内容"),
"contentEmptyTip": MessageLookupByLibrary.simpleMessage("内容不能为空"),
"contentScheme": MessageLookupByLibrary.simpleMessage("内容主题"),
"copy": MessageLookupByLibrary.simpleMessage("复制"),
"copyEnvVar": MessageLookupByLibrary.simpleMessage("复制环境变量"),
@@ -131,9 +147,8 @@ class MessageLookup extends MessageLookupByLibrary {
"delay": MessageLookupByLibrary.simpleMessage("延迟"),
"delaySort": MessageLookupByLibrary.simpleMessage("按延迟排序"),
"delete": MessageLookupByLibrary.simpleMessage("删除"),
"deleteColorTip": MessageLookupByLibrary.simpleMessage("确定删除当前颜色吗?"),
"deleteProfileTip": MessageLookupByLibrary.simpleMessage("确定要删除当前配置吗?"),
"deleteRuleTip": MessageLookupByLibrary.simpleMessage("确定要删除选中的规则吗?"),
"deleteMultipTip": m0,
"deleteTip": m1,
"desc": MessageLookupByLibrary.simpleMessage(
"基于ClashMeta的多平台代理客户端简单易用开源无广告。",
),
@@ -153,11 +168,13 @@ class MessageLookup extends MessageLookupByLibrary {
"domain": MessageLookupByLibrary.simpleMessage("域名"),
"download": MessageLookupByLibrary.simpleMessage("下载"),
"edit": MessageLookupByLibrary.simpleMessage("编辑"),
"emptyTip": m2,
"en": MessageLookupByLibrary.simpleMessage("英语"),
"enableOverride": MessageLookupByLibrary.simpleMessage("启用覆写"),
"entries": MessageLookupByLibrary.simpleMessage("个条目"),
"exclude": MessageLookupByLibrary.simpleMessage("从最近任务中隐藏"),
"excludeDesc": MessageLookupByLibrary.simpleMessage("应用在后台时,从最近任务中隐藏应用"),
"existsTip": m3,
"exit": MessageLookupByLibrary.simpleMessage("退出"),
"expand": MessageLookupByLibrary.simpleMessage("标准"),
"expirationTime": MessageLookupByLibrary.simpleMessage("到期时间"),
@@ -205,12 +222,16 @@ class MessageLookup extends MessageLookupByLibrary {
"icon": MessageLookupByLibrary.simpleMessage("图片"),
"iconConfiguration": MessageLookupByLibrary.simpleMessage("图片配置"),
"iconStyle": MessageLookupByLibrary.simpleMessage("图标样式"),
"import": MessageLookupByLibrary.simpleMessage("导入"),
"importFile": MessageLookupByLibrary.simpleMessage("通过文件导入"),
"importFromURL": MessageLookupByLibrary.simpleMessage("从URL导入"),
"importUrl": MessageLookupByLibrary.simpleMessage("通过URL导入"),
"infiniteTime": MessageLookupByLibrary.simpleMessage("长期有效"),
"init": MessageLookupByLibrary.simpleMessage("初始化"),
"inputCorrectHotkey": MessageLookupByLibrary.simpleMessage("请输入正确的快捷键"),
"intelligentSelected": MessageLookupByLibrary.simpleMessage("智能选择"),
"internet": MessageLookupByLibrary.simpleMessage("互联网"),
"interval": MessageLookupByLibrary.simpleMessage("间隔"),
"intranetIP": MessageLookupByLibrary.simpleMessage("内网 IP"),
"ipcidr": MessageLookupByLibrary.simpleMessage("IP/掩码"),
"ipv6Desc": MessageLookupByLibrary.simpleMessage("开启后将可以接收IPv6流量"),
@@ -219,7 +240,6 @@ class MessageLookup extends MessageLookupByLibrary {
"just": MessageLookupByLibrary.simpleMessage("刚刚"),
"keepAliveIntervalDesc": MessageLookupByLibrary.simpleMessage("TCP保持活动间隔"),
"key": MessageLookupByLibrary.simpleMessage(""),
"keyExists": MessageLookupByLibrary.simpleMessage("当前键已存在"),
"language": MessageLookupByLibrary.simpleMessage("语言"),
"layout": MessageLookupByLibrary.simpleMessage("布局"),
"light": MessageLookupByLibrary.simpleMessage("浅色"),
@@ -233,6 +253,7 @@ class MessageLookup extends MessageLookupByLibrary {
"logcatDesc": MessageLookupByLibrary.simpleMessage("禁用将会隐藏日志入口"),
"logs": MessageLookupByLibrary.simpleMessage("日志"),
"logsDesc": MessageLookupByLibrary.simpleMessage("日志捕获记录"),
"logsTest": MessageLookupByLibrary.simpleMessage("日志测试"),
"loopback": MessageLookupByLibrary.simpleMessage("回环解锁工具"),
"loopbackDesc": MessageLookupByLibrary.simpleMessage("用于UWP回环解锁"),
"loose": MessageLookupByLibrary.simpleMessage("宽松"),
@@ -243,6 +264,7 @@ class MessageLookup extends MessageLookupByLibrary {
"minimizeOnExit": MessageLookupByLibrary.simpleMessage("退出时最小化"),
"minimizeOnExitDesc": MessageLookupByLibrary.simpleMessage("修改系统默认退出事件"),
"minutes": MessageLookupByLibrary.simpleMessage("分钟"),
"mixedPort": MessageLookupByLibrary.simpleMessage("混合端口"),
"mode": MessageLookupByLibrary.simpleMessage("模式"),
"monochromeScheme": MessageLookupByLibrary.simpleMessage("单色"),
"months": MessageLookupByLibrary.simpleMessage(""),
@@ -269,14 +291,10 @@ class MessageLookup extends MessageLookupByLibrary {
"noProxyDesc": MessageLookupByLibrary.simpleMessage("请创建配置文件或者添加有效配置文件"),
"noResolve": MessageLookupByLibrary.simpleMessage("不解析IP"),
"none": MessageLookupByLibrary.simpleMessage(""),
"notEmpty": MessageLookupByLibrary.simpleMessage("不能为空"),
"notSelectedTip": MessageLookupByLibrary.simpleMessage("当前代理组无法选中"),
"nullConnectionsDesc": MessageLookupByLibrary.simpleMessage("暂无连接"),
"nullCoreInfoDesc": MessageLookupByLibrary.simpleMessage("无法获取内核信息"),
"nullLogsDesc": MessageLookupByLibrary.simpleMessage("暂无日志"),
"nullProfileDesc": MessageLookupByLibrary.simpleMessage("没有配置文件,请先添加配置文件"),
"nullProxies": MessageLookupByLibrary.simpleMessage("暂无代理"),
"nullRequestsDesc": MessageLookupByLibrary.simpleMessage("暂无请求"),
"nullTip": m4,
"numberTip": m5,
"oneColumn": MessageLookupByLibrary.simpleMessage("一列"),
"onlyIcon": MessageLookupByLibrary.simpleMessage("仅图标"),
"onlyOtherApps": MessageLookupByLibrary.simpleMessage("仅第三方应用"),
@@ -292,12 +310,13 @@ class MessageLookup extends MessageLookupByLibrary {
"overrideDesc": MessageLookupByLibrary.simpleMessage("覆写代理相关配置"),
"overrideDns": MessageLookupByLibrary.simpleMessage("覆写DNS"),
"overrideDnsDesc": MessageLookupByLibrary.simpleMessage("开启后将覆盖配置中的DNS选项"),
"overrideInvalidTip": MessageLookupByLibrary.simpleMessage("在脚本模式下不生效"),
"overrideOriginRules": MessageLookupByLibrary.simpleMessage("覆盖原始规则"),
"palette": MessageLookupByLibrary.simpleMessage("调色板"),
"password": MessageLookupByLibrary.simpleMessage("密码"),
"passwordTip": MessageLookupByLibrary.simpleMessage("密码不能为空"),
"paste": MessageLookupByLibrary.simpleMessage("粘贴"),
"pleaseBindWebDAV": MessageLookupByLibrary.simpleMessage("请绑定WebDAV"),
"pleaseEnterScriptName": MessageLookupByLibrary.simpleMessage("请输入脚本名称"),
"pleaseInputAdminPassword": MessageLookupByLibrary.simpleMessage(
"请输入管理员密码",
),
@@ -306,6 +325,8 @@ class MessageLookup extends MessageLookupByLibrary {
"请上传有效的二维码",
),
"port": MessageLookupByLibrary.simpleMessage("端口"),
"portConflictTip": MessageLookupByLibrary.simpleMessage("请输入不同的端口"),
"portTip": m6,
"preferH3Desc": MessageLookupByLibrary.simpleMessage("优先使用DOH的http/3"),
"pressKeyboard": MessageLookupByLibrary.simpleMessage("请按下按键"),
"preview": MessageLookupByLibrary.simpleMessage("预览"),
@@ -346,13 +367,18 @@ class MessageLookup extends MessageLookupByLibrary {
"recovery": MessageLookupByLibrary.simpleMessage("恢复"),
"recoveryAll": MessageLookupByLibrary.simpleMessage("恢复所有数据"),
"recoveryProfiles": MessageLookupByLibrary.simpleMessage("仅恢复配置文件"),
"recoveryStrategy": MessageLookupByLibrary.simpleMessage("恢复策略"),
"recoveryStrategy_compatible": MessageLookupByLibrary.simpleMessage("兼容"),
"recoveryStrategy_override": MessageLookupByLibrary.simpleMessage("覆盖"),
"recoverySuccess": MessageLookupByLibrary.simpleMessage("恢复成功"),
"redirPort": MessageLookupByLibrary.simpleMessage("Redir端口"),
"redo": MessageLookupByLibrary.simpleMessage("重做"),
"regExp": MessageLookupByLibrary.simpleMessage("正则"),
"remote": MessageLookupByLibrary.simpleMessage("远程"),
"remoteBackupDesc": MessageLookupByLibrary.simpleMessage("备份数据到WebDAV"),
"remoteRecoveryDesc": MessageLookupByLibrary.simpleMessage("通过WebDAV恢复数据"),
"remove": MessageLookupByLibrary.simpleMessage("移除"),
"rename": MessageLookupByLibrary.simpleMessage("重命名"),
"requests": MessageLookupByLibrary.simpleMessage("请求"),
"requestsDesc": MessageLookupByLibrary.simpleMessage("查看最近请求记录"),
"reset": MessageLookupByLibrary.simpleMessage("重置"),
@@ -371,24 +397,24 @@ class MessageLookup extends MessageLookupByLibrary {
"ru": MessageLookupByLibrary.simpleMessage("俄语"),
"rule": MessageLookupByLibrary.simpleMessage("规则"),
"ruleName": MessageLookupByLibrary.simpleMessage("规则名称"),
"ruleProviderEmptyTip": MessageLookupByLibrary.simpleMessage("规则提供者不能为空"),
"ruleProviders": MessageLookupByLibrary.simpleMessage("规则提供者"),
"ruleTarget": MessageLookupByLibrary.simpleMessage("规则目标"),
"ruleTargetEmptyTip": MessageLookupByLibrary.simpleMessage("规则目标不能为空"),
"save": MessageLookupByLibrary.simpleMessage("保存"),
"saveChanges": MessageLookupByLibrary.simpleMessage("是否保存更改?"),
"saveTip": MessageLookupByLibrary.simpleMessage("确定要保存吗?"),
"script": MessageLookupByLibrary.simpleMessage("脚本"),
"search": MessageLookupByLibrary.simpleMessage("搜索"),
"seconds": MessageLookupByLibrary.simpleMessage(""),
"selectAll": MessageLookupByLibrary.simpleMessage("全选"),
"selected": MessageLookupByLibrary.simpleMessage("已选择"),
"selectedCountTitle": m0,
"selectedCountTitle": m7,
"settings": MessageLookupByLibrary.simpleMessage("设置"),
"show": MessageLookupByLibrary.simpleMessage("显示"),
"shrink": MessageLookupByLibrary.simpleMessage("紧凑"),
"silentLaunch": MessageLookupByLibrary.simpleMessage("静默启动"),
"silentLaunchDesc": MessageLookupByLibrary.simpleMessage("后台启动"),
"size": MessageLookupByLibrary.simpleMessage("尺寸"),
"socksPort": MessageLookupByLibrary.simpleMessage("Socks端口"),
"sort": MessageLookupByLibrary.simpleMessage("排序"),
"source": MessageLookupByLibrary.simpleMessage("来源"),
"sourceIp": MessageLookupByLibrary.simpleMessage("源IP"),
@@ -402,7 +428,6 @@ class MessageLookup extends MessageLookupByLibrary {
"stopVpn": MessageLookupByLibrary.simpleMessage("正在停止VPN..."),
"style": MessageLookupByLibrary.simpleMessage("风格"),
"subRule": MessageLookupByLibrary.simpleMessage("子规则"),
"subRuleEmptyTip": MessageLookupByLibrary.simpleMessage("子规则内容不能为空"),
"submit": MessageLookupByLibrary.simpleMessage("提交"),
"sync": MessageLookupByLibrary.simpleMessage("同步"),
"system": MessageLookupByLibrary.simpleMessage("系统"),
@@ -428,6 +453,7 @@ class MessageLookup extends MessageLookupByLibrary {
"toggle": MessageLookupByLibrary.simpleMessage("切换"),
"tonalSpotScheme": MessageLookupByLibrary.simpleMessage("调性点缀"),
"tools": MessageLookupByLibrary.simpleMessage("工具"),
"tproxyPort": MessageLookupByLibrary.simpleMessage("Tproxy端口"),
"trafficUsage": MessageLookupByLibrary.simpleMessage("流量统计"),
"tun": MessageLookupByLibrary.simpleMessage("虚拟网卡"),
"tunDesc": MessageLookupByLibrary.simpleMessage("仅在管理员模式生效"),
@@ -439,14 +465,15 @@ class MessageLookup extends MessageLookupByLibrary {
"unifiedDelay": MessageLookupByLibrary.simpleMessage("统一延迟"),
"unifiedDelayDesc": MessageLookupByLibrary.simpleMessage("去除握手等额外延迟"),
"unknown": MessageLookupByLibrary.simpleMessage("未知"),
"unnamed": MessageLookupByLibrary.simpleMessage("未命名"),
"update": MessageLookupByLibrary.simpleMessage("更新"),
"upload": MessageLookupByLibrary.simpleMessage("上传"),
"url": MessageLookupByLibrary.simpleMessage("URL"),
"urlDesc": MessageLookupByLibrary.simpleMessage("通过URL获取配置文件"),
"urlTip": m8,
"useHosts": MessageLookupByLibrary.simpleMessage("使用Hosts"),
"useSystemHosts": MessageLookupByLibrary.simpleMessage("使用系统Hosts"),
"value": MessageLookupByLibrary.simpleMessage(""),
"valueExists": MessageLookupByLibrary.simpleMessage("当前值已存在"),
"vibrantScheme": MessageLookupByLibrary.simpleMessage("活力"),
"view": MessageLookupByLibrary.simpleMessage("查看"),
"vpnDesc": MessageLookupByLibrary.simpleMessage("修改VPN相关设置"),

View File

@@ -140,16 +140,6 @@ class AppLocalizations {
return Intl.message('Core info', name: 'coreInfo', desc: '', args: []);
}
/// `Unable to obtain core info`
String get nullCoreInfoDesc {
return Intl.message(
'Unable to obtain core info',
name: 'nullCoreInfoDesc',
desc: '',
args: [],
);
}
/// `Network speed`
String get networkSpeed {
return Intl.message(
@@ -215,11 +205,6 @@ class AppLocalizations {
);
}
/// `No logs`
String get nullLogsDesc {
return Intl.message('No logs', name: 'nullLogsDesc', desc: '', args: []);
}
/// `Settings`
String get settings {
return Intl.message('Settings', name: 'settings', desc: '', args: []);
@@ -1160,26 +1145,6 @@ class AppLocalizations {
return Intl.message('Password', name: 'password', desc: '', args: []);
}
/// `Password cannot be empty`
String get passwordTip {
return Intl.message(
'Password cannot be empty',
name: 'passwordTip',
desc: '',
args: [],
);
}
/// `Account cannot be empty`
String get accountTip {
return Intl.message(
'Account cannot be empty',
name: 'accountTip',
desc: '',
args: [],
);
}
/// `Check for updates`
String get checkUpdate {
return Intl.message(
@@ -1465,26 +1430,6 @@ class AppLocalizations {
);
}
/// `No requests`
String get nullRequestsDesc {
return Intl.message(
'No requests',
name: 'nullRequestsDesc',
desc: '',
args: [],
);
}
/// `No connections`
String get nullConnectionsDesc {
return Intl.message(
'No connections',
name: 'nullConnectionsDesc',
desc: '',
args: [],
);
}
/// `Intranet IP`
String get intranetIP {
return Intl.message('Intranet IP', name: 'intranetIP', desc: '', args: []);
@@ -1700,16 +1645,6 @@ class AppLocalizations {
);
}
/// `Sure you want to delete the current profile?`
String get deleteProfileTip {
return Intl.message(
'Sure you want to delete the current profile?',
name: 'deleteProfileTip',
desc: '',
args: [],
);
}
/// `Pure black mode`
String get pureBlackMode {
return Intl.message(
@@ -1930,16 +1865,6 @@ class AppLocalizations {
return Intl.message('Value', name: 'value', desc: '', args: []);
}
/// `Cannot be empty`
String get notEmpty {
return Intl.message(
'Cannot be empty',
name: 'notEmpty',
desc: '',
args: [],
);
}
/// `Add Hosts`
String get hostsDesc {
return Intl.message('Add Hosts', name: 'hostsDesc', desc: '', args: []);
@@ -2615,11 +2540,6 @@ class AppLocalizations {
);
}
/// `No proxies`
String get nullProxies {
return Intl.message('No proxies', name: 'nullProxies', desc: '', args: []);
}
/// `Copy success`
String get copySuccess {
return Intl.message(
@@ -2665,26 +2585,6 @@ class AppLocalizations {
return Intl.message('Listen', name: 'listen', desc: '', args: []);
}
/// `The current key already exists`
String get keyExists {
return Intl.message(
'The current key already exists',
name: 'keyExists',
desc: '',
args: [],
);
}
/// `The current value already exists`
String get valueExists {
return Intl.message(
'The current value already exists',
name: 'valueExists',
desc: '',
args: [],
);
}
/// `undo`
String get undo {
return Intl.message('undo', name: 'undo', desc: '', args: []);
@@ -2735,16 +2635,6 @@ class AppLocalizations {
return Intl.message('Add rule', name: 'addRule', desc: '', args: []);
}
/// `Rule provider cannot be empty`
String get ruleProviderEmptyTip {
return Intl.message(
'Rule provider cannot be empty',
name: 'ruleProviderEmptyTip',
desc: '',
args: [],
);
}
/// `Rule name`
String get ruleName {
return Intl.message('Rule name', name: 'ruleName', desc: '', args: []);
@@ -2755,46 +2645,16 @@ class AppLocalizations {
return Intl.message('Content', name: 'content', desc: '', args: []);
}
/// `Content cannot be empty`
String get contentEmptyTip {
return Intl.message(
'Content cannot be empty',
name: 'contentEmptyTip',
desc: '',
args: [],
);
}
/// `Sub rule`
String get subRule {
return Intl.message('Sub rule', name: 'subRule', desc: '', args: []);
}
/// `Sub rule content cannot be empty`
String get subRuleEmptyTip {
return Intl.message(
'Sub rule content cannot be empty',
name: 'subRuleEmptyTip',
desc: '',
args: [],
);
}
/// `Rule target`
String get ruleTarget {
return Intl.message('Rule target', name: 'ruleTarget', desc: '', args: []);
}
/// `Rule target cannot be empty`
String get ruleTargetEmptyTip {
return Intl.message(
'Rule target cannot be empty',
name: 'ruleTargetEmptyTip',
desc: '',
args: [],
);
}
/// `Source IP`
String get sourceIp {
return Intl.message('Source IP', name: 'sourceIp', desc: '', args: []);
@@ -2845,16 +2705,6 @@ class AppLocalizations {
);
}
/// `Are you sure you want to delete the selected rule?`
String get deleteRuleTip {
return Intl.message(
'Are you sure you want to delete the selected rule?',
name: 'deleteRuleTip',
desc: '',
args: [],
);
}
/// `Do you want to save the changes?`
String get saveChanges {
return Intl.message(
@@ -2905,26 +2755,6 @@ class AppLocalizations {
);
}
/// `Are you sure you want to delete the current color?`
String get deleteColorTip {
return Intl.message(
'Are you sure you want to delete the current color?',
name: 'deleteColorTip',
desc: '',
args: [],
);
}
/// `Current color already exists`
String get colorExists {
return Intl.message(
'Current color already exists',
name: 'colorExists',
desc: '',
args: [],
);
}
/// `Color schemes`
String get colorSchemes {
return Intl.message(
@@ -3070,15 +2900,245 @@ class AppLocalizations {
return Intl.message('System APP', name: 'systemApp', desc: '', args: []);
}
/// `No network App`
/// `No network APP`
String get noNetworkApp {
return Intl.message(
'No network App',
'No network APP',
name: 'noNetworkApp',
desc: '',
args: [],
);
}
/// `Contact me`
String get contactMe {
return Intl.message('Contact me', name: 'contactMe', desc: '', args: []);
}
/// `Recovery strategy`
String get recoveryStrategy {
return Intl.message(
'Recovery strategy',
name: 'recoveryStrategy',
desc: '',
args: [],
);
}
/// `Override`
String get recoveryStrategy_override {
return Intl.message(
'Override',
name: 'recoveryStrategy_override',
desc: '',
args: [],
);
}
/// `Compatible`
String get recoveryStrategy_compatible {
return Intl.message(
'Compatible',
name: 'recoveryStrategy_compatible',
desc: '',
args: [],
);
}
/// `Logs test`
String get logsTest {
return Intl.message('Logs test', name: 'logsTest', desc: '', args: []);
}
/// `{label} cannot be empty`
String emptyTip(Object label) {
return Intl.message(
'$label cannot be empty',
name: 'emptyTip',
desc: '',
args: [label],
);
}
/// `{label} must be a url`
String urlTip(Object label) {
return Intl.message(
'$label must be a url',
name: 'urlTip',
desc: '',
args: [label],
);
}
/// `{label} must be a number`
String numberTip(Object label) {
return Intl.message(
'$label must be a number',
name: 'numberTip',
desc: '',
args: [label],
);
}
/// `Interval`
String get interval {
return Intl.message('Interval', name: 'interval', desc: '', args: []);
}
/// `Current {label} already exists`
String existsTip(Object label) {
return Intl.message(
'Current $label already exists',
name: 'existsTip',
desc: '',
args: [label],
);
}
/// `Are you sure you want to delete the current {label}?`
String deleteTip(Object label) {
return Intl.message(
'Are you sure you want to delete the current $label?',
name: 'deleteTip',
desc: '',
args: [label],
);
}
/// `Are you sure you want to delete the selected {label}?`
String deleteMultipTip(Object label) {
return Intl.message(
'Are you sure you want to delete the selected $label?',
name: 'deleteMultipTip',
desc: '',
args: [label],
);
}
/// `No {label} at the moment`
String nullTip(Object label) {
return Intl.message(
'No $label at the moment',
name: 'nullTip',
desc: '',
args: [label],
);
}
/// `Script`
String get script {
return Intl.message('Script', name: 'script', desc: '', args: []);
}
/// `Color`
String get color {
return Intl.message('Color', name: 'color', desc: '', args: []);
}
/// `Rename`
String get rename {
return Intl.message('Rename', name: 'rename', desc: '', args: []);
}
/// `Unnamed`
String get unnamed {
return Intl.message('Unnamed', name: 'unnamed', desc: '', args: []);
}
/// `Please enter a script name`
String get pleaseEnterScriptName {
return Intl.message(
'Please enter a script name',
name: 'pleaseEnterScriptName',
desc: '',
args: [],
);
}
/// `Does not take effect in script mode`
String get overrideInvalidTip {
return Intl.message(
'Does not take effect in script mode',
name: 'overrideInvalidTip',
desc: '',
args: [],
);
}
/// `Mixed Port`
String get mixedPort {
return Intl.message('Mixed Port', name: 'mixedPort', desc: '', args: []);
}
/// `Socks Port`
String get socksPort {
return Intl.message('Socks Port', name: 'socksPort', desc: '', args: []);
}
/// `Redir Port`
String get redirPort {
return Intl.message('Redir Port', name: 'redirPort', desc: '', args: []);
}
/// `Tproxy Port`
String get tproxyPort {
return Intl.message('Tproxy Port', name: 'tproxyPort', desc: '', args: []);
}
/// `{label} must be between 1024 and 49151`
String portTip(Object label) {
return Intl.message(
'$label must be between 1024 and 49151',
name: 'portTip',
desc: '',
args: [label],
);
}
/// `Please enter a different port`
String get portConflictTip {
return Intl.message(
'Please enter a different port',
name: 'portConflictTip',
desc: '',
args: [],
);
}
/// `Import`
String get import {
return Intl.message('Import', name: 'import', desc: '', args: []);
}
/// `Import from file`
String get importFile {
return Intl.message(
'Import from file',
name: 'importFile',
desc: '',
args: [],
);
}
/// `Import from URL`
String get importUrl {
return Intl.message(
'Import from URL',
name: 'importUrl',
desc: '',
args: [],
);
}
/// `Auto set system DNS`
String get autoSetSystemDns {
return Intl.message(
'Auto set system DNS',
name: 'autoSetSystemDns',
desc: '',
args: [],
);
}
}
class AppLocalizationDelegate extends LocalizationsDelegate<AppLocalizations> {

View File

@@ -5,7 +5,6 @@ import 'dart:io';
import 'dart:isolate';
import 'dart:ui';
import 'package:fl_clash/models/core.dart';
import 'package:fl_clash/plugins/app.dart';
import 'package:fl_clash/plugins/tile.dart';
import 'package:fl_clash/plugins/vpn.dart';
@@ -17,6 +16,7 @@ import 'application.dart';
import 'clash/core.dart';
import 'clash/lib.dart';
import 'common/common.dart';
import 'models/models.dart';
Future<void> main() async {
globalState.isService = false;
@@ -26,7 +26,6 @@ Future<void> main() async {
await globalState.initApp(version);
await android?.init();
await window?.init(version);
globalState.isPre = const String.fromEnvironment("APP_ENV") != 'stable';
HttpOverrides.global = FlClashHttpOverrides();
runApp(ProviderScope(
child: const Application(),
@@ -75,27 +74,35 @@ Future<void> _service(List<String> flags) async {
app?.tip(appLocalizations.startVpn);
final homeDirPath = await appPath.homeDirPath;
final version = await system.version;
clashLibHandler
.quickStart(
InitParams(
homeDir: homeDirPath,
version: version,
),
globalState.getUpdateConfigParams(),
globalState.getCoreState(),
)
.then(
(res) async {
if (res.isNotEmpty) {
await vpn?.stop();
exit(0);
}
await vpn?.start(
clashLibHandler.getAndroidVpnOptions(),
);
clashLibHandler.startListener();
},
final clashConfig = globalState.config.patchClashConfig.copyWith.tun(
enable: false,
);
Future(() async {
final profileId = globalState.config.currentProfileId;
if (profileId == null) {
return;
}
final params = await globalState.getSetupParams(
pathConfig: clashConfig,
);
final res = await clashLibHandler.quickStart(
InitParams(
homeDir: homeDirPath,
version: version,
),
params,
globalState.getCoreState(),
);
debugPrint(res);
if (res.isNotEmpty) {
await vpn?.stop();
exit(0);
}
await vpn?.start(
clashLibHandler.getAndroidVpnOptions(),
);
clashLibHandler.startListener();
});
}
}

View File

@@ -1,11 +1,13 @@
import 'dart:async';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/providers/providers.dart';
import 'package:fl_clash/state.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class AppStateManager extends StatefulWidget {
class AppStateManager extends ConsumerStatefulWidget {
final Widget child;
const AppStateManager({
@@ -14,19 +16,59 @@ class AppStateManager extends StatefulWidget {
});
@override
State<AppStateManager> createState() => _AppStateManagerState();
ConsumerState<AppStateManager> createState() => _AppStateManagerState();
}
class _AppStateManagerState extends State<AppStateManager>
class _AppStateManagerState extends ConsumerState<AppStateManager>
with WidgetsBindingObserver {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
ref.listenManual(layoutChangeProvider, (prev, next) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (prev != next) {
globalState.cacheHeightMap = {};
}
});
});
ref.listenManual(
checkIpProvider,
(prev, next) {
if (prev != next && next.b) {
detectionState.startCheck();
}
},
fireImmediately: true,
);
ref.listenManual(configStateProvider, (prev, next) {
if (prev != next) {
globalState.appController.savePreferencesDebounce();
}
});
ref.listenManual(
autoSetSystemDnsStateProvider,
(prev, next) async {
if (prev == next) {
return;
}
if (next.a == true && next.b == true) {
system.setMacOSDns(false);
} else {
system.setMacOSDns(true);
}
},
);
}
@override
void dispose() {
reassemble() {
super.reassemble();
}
@override
void dispose() async {
await system.setMacOSDns(true);
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}

View File

@@ -32,7 +32,7 @@ class _ClashContainerState extends ConsumerState<ClashManager>
void initState() {
super.initState();
clashMessage.addListener(this);
ref.listenManual(currentProfileIdProvider, (prev, next) {
ref.listenManual(needSetupProvider, (prev, next) {
if (prev != next) {
globalState.appController.handleChangeProfile();
}
@@ -42,11 +42,22 @@ class _ClashContainerState extends ConsumerState<ClashManager>
await clashCore.setState(next);
}
});
ref.listenManual(clashConfigStateProvider, (prev, next) {
ref.listenManual(updateParamsProvider, (prev, next) {
if (prev != next) {
globalState.appController.updateClashConfigDebounce();
}
});
ref.listenManual(
appSettingProvider.select((state) => state.openLogs),
(prev, next) {
if (next) {
clashCore.startLog();
} else {
clashCore.stopLog();
}
},
);
}
@override
@@ -61,7 +72,7 @@ class _ClashContainerState extends ConsumerState<ClashManager>
final appController = globalState.appController;
appController.setDelay(delay);
debouncer.call(
DebounceTag.updateDelay,
FunctionTag.updateDelay,
() async {
await appController.updateGroupsDebounce();
},
@@ -71,7 +82,7 @@ class _ClashContainerState extends ConsumerState<ClashManager>
@override
void onLog(Log log) {
ref.watch(logsProvider.notifier).addLog(log);
ref.read(logsProvider.notifier).addLog(log);
if (log.logLevel == LogLevel.error) {
globalState.showNotifier(log.payload);
}
@@ -80,13 +91,13 @@ class _ClashContainerState extends ConsumerState<ClashManager>
@override
void onRequest(Connection connection) async {
ref.watch(requestsProvider.notifier).addRequest(connection);
ref.read(requestsProvider.notifier).addRequest(connection);
super.onRequest(connection);
}
@override
Future<void> onLoaded(String providerName) async {
ref.watch(providersProvider.notifier).setProvider(
ref.read(providersProvider.notifier).setProvider(
await clashCore.getExternalProvider(
providerName,
),

View File

@@ -4,7 +4,7 @@ import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter/material.dart';
class ConnectivityManager extends StatefulWidget {
final VoidCallback? onConnectivityChanged;
final Function(List<ConnectivityResult> results)? onConnectivityChanged;
final Widget child;
const ConnectivityManager({
@@ -23,9 +23,9 @@ class _ConnectivityManagerState extends State<ConnectivityManager> {
@override
void initState() {
super.initState();
subscription = Connectivity().onConnectivityChanged.listen((_) async {
subscription = Connectivity().onConnectivityChanged.listen((results) async {
if (widget.onConnectivityChanged != null) {
widget.onConnectivityChanged!();
widget.onConnectivityChanged!(results);
}
});
}

View File

@@ -8,7 +8,7 @@ import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:hotkey_manager/hotkey_manager.dart';
class HotKeyManager extends StatelessWidget {
class HotKeyManager extends ConsumerStatefulWidget {
final Widget child;
const HotKeyManager({
@@ -16,6 +16,25 @@ class HotKeyManager extends StatelessWidget {
required this.child,
});
@override
ConsumerState<HotKeyManager> createState() => _HotKeyManagerState();
}
class _HotKeyManagerState extends ConsumerState<HotKeyManager> {
@override
void initState() {
super.initState();
ref.listenManual(
hotKeyActionsProvider,
(prev, next) {
if (!hotKeyActionListEquality.equals(prev, next)) {
_updateHotKeys(hotKeyActions: next);
}
},
fireImmediately: true,
);
}
_handleHotKeyAction(HotAction action) async {
switch (action) {
case HotAction.mode:
@@ -59,22 +78,30 @@ class HotKeyManager extends StatelessWidget {
await Future.wait(hotkeyActionHandles);
}
_buildShortcuts(Widget child) {
return Shortcuts(
shortcuts: {
utils.controlSingleActivator(LogicalKeyboardKey.keyW):
CloseWindowIntent(),
},
child: Actions(
actions: {
CloseWindowIntent: CallbackAction<CloseWindowIntent>(
onInvoke: (_) => globalState.appController.handleBackOrExit(),
),
DoNothingIntent: CallbackAction<DoNothingIntent>(
onInvoke: (_) => null,
),
},
child: child,
),
);
}
@override
Widget build(BuildContext context) {
return Consumer(
builder: (_, ref, child) {
ref.listenManual(
hotKeyActionsProvider,
(prev, next) {
if (!hotKeyActionListEquality.equals(prev, next)) {
_updateHotKeys(hotKeyActions: next);
}
},
fireImmediately: true,
);
return child!;
},
child: child,
return _buildShortcuts(
widget.child,
);
}
}

View File

@@ -39,6 +39,7 @@ class MessageManagerState extends State<MessageManager> {
id: utils.uuidV4,
text: text,
);
commonPrint.log(text);
_bufferMessages.add(commonMessage);
await _showMessage();
}
@@ -87,32 +88,32 @@ class MessageManagerState extends State<MessageManager> {
child: messages.isEmpty
? SizedBox()
: LayoutBuilder(
key: Key(messages.last.id),
builder: (_, constraints) {
return Card(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(12.0),
),
key: Key(messages.last.id),
builder: (_, constraints) {
return Card(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(12.0),
),
),
elevation: 10,
color: context.colorScheme.surfaceContainerHigh,
child: Container(
width: min(
constraints.maxWidth,
500,
),
padding: EdgeInsets.symmetric(
horizontal: 12,
vertical: 16,
),
child: Text(
messages.last.text,
),
),
);
},
),
elevation: 10,
color: context.colorScheme.surfaceContainerHigh,
child: Container(
width: min(
constraints.maxWidth,
500,
),
padding: EdgeInsets.symmetric(
horizontal: 12,
vertical: 16,
),
child: Text(
messages.last.text,
),
),
);
},
),
);
},
),

View File

@@ -23,9 +23,9 @@ class ThemeManager extends ConsumerWidget {
final double textScaleFactor = max(
min(
textScale.enable ? textScale.scale : defaultTextScaleFactor,
1.2,
maxTextScale,
),
0.8,
minTextScale,
);
globalState.measure = Measure.of(context, textScaleFactor);
@@ -38,7 +38,7 @@ class ThemeManager extends ConsumerWidget {
textScaleFactor,
),
padding: padding.copyWith(
top: padding.top > height * 0.3 ? 0.0 : padding.top,
top: padding.top > height * 0.3 ? 20.0 : padding.top,
),
),
child: LayoutBuilder(

View File

@@ -29,7 +29,7 @@ class _VpnContainerState extends ConsumerState<VpnManager> {
showTip() {
debouncer.call(
DebounceTag.vpnTip,
FunctionTag.vpnTip,
() {
if (ref.read(runTimeProvider.notifier).isStart) {
globalState.showNotifier(

View File

@@ -38,7 +38,7 @@ class _WindowContainerState extends ConsumerState<WindowManager>
(prev, next) {
if (prev != next) {
debouncer.call(
DebounceTag.autoLaunch,
FunctionTag.autoLaunch,
() {
autoLaunch?.updateStatus(next);
},
@@ -102,9 +102,10 @@ class _WindowContainerState extends ConsumerState<WindowManager>
}
@override
Future<void> onTaskbarCreated() async {
globalState.appController.updateTray(true);
super.onTaskbarCreated();
void onWindowRestore() {
commonPrint.log("restore");
render?.resume();
super.onWindowRestore();
}
@override

View File

@@ -14,6 +14,7 @@ typedef DelayMap = Map<String, Map<String, int?>>;
class AppState with _$AppState {
const factory AppState({
@Default(false) bool isInit,
@Default(false) bool backBlock,
@Default(PageLabel.dashboard) PageLabel pageLabel,
@Default([]) List<Package> packages,
@Default(0) int sortNum,
@@ -30,7 +31,8 @@ class AppState with _$AppState {
required FixedList<Log> logs,
required FixedList<Traffic> traffics,
required Traffic totalTraffic,
@Default(false) bool needApply,
@Default("") String proxiesQuery,
@Default(false) bool realTunEnable,
}) = _AppState;
}

View File

@@ -1,12 +1,10 @@
// ignore_for_file: invalid_annotation_target
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import '../enum/enum.dart';
part 'generated/clash_config.freezed.dart';
part 'generated/clash_config.g.dart';
typedef HostsMap = Map<String, String>;
@@ -142,13 +140,48 @@ class RuleProvider with _$RuleProvider {
_$RuleProviderFromJson(json);
}
@freezed
class Sniffer with _$Sniffer {
const factory Sniffer({
@Default(false) bool enable,
@Default(true) @JsonKey(name: "override-destination") bool overrideDest,
@Default([]) List<String> sniffing,
@Default([]) @JsonKey(name: "force-domain") List<String> forceDomain,
@Default([]) @JsonKey(name: "skip-src-address") List<String> skipSrcAddress,
@Default([]) @JsonKey(name: "skip-dst-address") List<String> skipDstAddress,
@Default([]) @JsonKey(name: "skip-domain") List<String> skipDomain,
@Default([]) @JsonKey(name: "port-whitelist") List<String> port,
@Default(true) @JsonKey(name: "force-dns-mapping") bool forceDnsMapping,
@Default(true) @JsonKey(name: "parse-pure-ip") bool parsePureIp,
@Default({}) Map<String, SnifferConfig> sniff,
}) = _Sniffer;
factory Sniffer.fromJson(Map<String, Object?> json) =>
_$SnifferFromJson(json);
}
List<String> _formJsonPorts(List? ports) {
return ports?.map((item) => item.toString()).toList() ?? [];
}
@freezed
class SnifferConfig with _$SnifferConfig {
const factory SnifferConfig({
@Default([]) @JsonKey(fromJson: _formJsonPorts) List<String> ports,
@JsonKey(name: "override-destination") bool? overrideDest,
}) = _SnifferConfig;
factory SnifferConfig.fromJson(Map<String, Object?> json) =>
_$SnifferConfigFromJson(json);
}
@freezed
class Tun with _$Tun {
const factory Tun({
@Default(false) bool enable,
@Default(appName) String device,
@JsonKey(name: "auto-route") @Default(false) bool autoRoute,
@Default(TunStack.gvisor) TunStack stack,
@Default(TunStack.mixed) TunStack stack,
@JsonKey(name: "dns-hijack") @Default(["any:53"]) List<String> dnsHijack,
@JsonKey(name: "route-address") @Default([]) List<String> routeAddress,
}) = _Tun;
@@ -408,7 +441,7 @@ List<SubRule> _genSubRules(Map<String, dynamic> json) {
class ClashConfigSnippet with _$ClashConfigSnippet {
const factory ClashConfigSnippet({
@Default([]) @JsonKey(name: "proxy-groups") List<ProxyGroup> proxyGroups,
@JsonKey(fromJson: _genRule) @Default([]) List<Rule> rule,
@JsonKey(fromJson: _genRule, name: "rules") @Default([]) List<Rule> rule,
@JsonKey(name: "rule-providers", fromJson: _genRuleProviders)
@Default([])
List<RuleProvider> ruleProvider,
@@ -425,6 +458,10 @@ class ClashConfigSnippet with _$ClashConfigSnippet {
class ClashConfig with _$ClashConfig {
const factory ClashConfig({
@Default(defaultMixedPort) @JsonKey(name: "mixed-port") int mixedPort,
@Default(0) @JsonKey(name: "socks-port") int socksPort,
@Default(0) @JsonKey(name: "port") int port,
@Default(0) @JsonKey(name: "redir-port") int redirPort,
@Default(0) @JsonKey(name: "tproxy-port") int tproxyPort,
@Default(Mode.rule) Mode mode,
@Default(false) @JsonKey(name: "allow-lan") bool allowLan,
@Default(LogLevel.error) @JsonKey(name: "log-level") LogLevel logLevel,

View File

@@ -8,7 +8,6 @@ import 'package:flutter/material.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'generated/common.freezed.dart';
part 'generated/common.g.dart';
@freezed
@@ -17,7 +16,7 @@ class NavigationItem with _$NavigationItem {
required Icon icon,
required PageLabel label,
final String? description,
required Widget fragment,
required Widget view,
@Default(true) bool keep,
String? path,
@Default([NavigationItemMode.mobile, NavigationItemMode.desktop])
@@ -120,6 +119,7 @@ class LogsState with _$LogsState {
@Default([]) List<Log> logs,
@Default([]) List<String> keywords,
@Default("") String query,
@Default(false) bool loading,
}) = _LogsState;
}
@@ -128,10 +128,10 @@ extension LogsStateExt on LogsState {
final lowQuery = query.toLowerCase();
return logs.where(
(log) {
final payload = log.payload.toLowerCase();
final logLevelName = log.logLevel.name;
return {logLevelName}.containsAll(keywords) &&
((payload.contains(lowQuery)) || logLevelName.contains(lowQuery));
((log.payload.toLowerCase().contains(lowQuery)) ||
logLevelName.contains(lowQuery));
},
).toList();
}
@@ -143,6 +143,7 @@ class ConnectionsState with _$ConnectionsState {
@Default([]) List<Connection> connections,
@Default([]) List<String> keywords,
@Default("") String query,
@Default(false) bool loading,
}) = _ConnectionsState;
}
@@ -502,13 +503,76 @@ class PopupMenuItemData {
this.icon,
required this.label,
required this.onPressed,
this.type,
this.iconSize,
});
final double? iconSize;
final String label;
final VoidCallback? onPressed;
final IconData? icon;
final PopupMenuItemType? type;
}
@freezed
class TextPainterParams with _$TextPainterParams {
const factory TextPainterParams({
required String? text,
required double? fontSize,
required double textScaleFactor,
@Default(double.infinity) double maxWidth,
int? maxLines,
}) = _TextPainterParams;
factory TextPainterParams.fromJson(Map<String, Object?> json) =>
_$TextPainterParamsFromJson(json);
}
class CloseWindowIntent extends Intent {
const CloseWindowIntent();
}
@freezed
class Result<T> with _$Result<T> {
const factory Result({
required T? data,
required ResultType type,
required String message,
}) = _Result;
factory Result.success(T data) => Result(
data: data,
type: ResultType.success,
message: "",
);
factory Result.error(String message) => Result(
data: null,
type: ResultType.error,
message: message,
);
}
extension ResultExt on Result {
bool get isError => type == ResultType.error;
bool get isSuccess => type == ResultType.success;
}
@freezed
class Script with _$Script {
const factory Script({
required String id,
required String label,
required String content,
}) = _Script;
factory Script.create({
required String label,
required String content,
}) {
return Script(
id: utils.uuidV4,
label: label,
content: content,
);
}
factory Script.fromJson(Map<String, Object?> json) => _$ScriptFromJson(json);
}

View File

@@ -8,7 +8,6 @@ import 'package:freezed_annotation/freezed_annotation.dart';
import 'models.dart';
part 'generated/config.freezed.dart';
part 'generated/config.g.dart';
const defaultBypassDomain = [
@@ -75,7 +74,7 @@ class AppSettingProps with _$AppSettingProps {
@Default(false) bool autoLaunch,
@Default(false) bool silentLaunch,
@Default(false) bool autoRun,
@Default(true) bool openLogs,
@Default(false) bool openLogs,
@Default(true) bool closeConnections,
@Default(defaultTestUrl) String testUrl,
@Default(true) bool isAnimateToPage,
@@ -85,6 +84,7 @@ class AppSettingProps with _$AppSettingProps {
@Default(true) bool minimizeOnExit,
@Default(false) bool hidden,
@Default(false) bool developerMode,
@Default(RecoveryStrategy.compatible) RecoveryStrategy recoveryStrategy,
}) = _AppSettingProps;
factory AppSettingProps.fromJson(Map<String, Object?> json) =>
@@ -152,7 +152,8 @@ class NetworkProps with _$NetworkProps {
const factory NetworkProps({
@Default(true) bool systemProxy,
@Default(defaultBypassDomain) List<String> bypassDomain,
@Default(RouteMode.bypassPrivate) RouteMode routeMode,
@Default(RouteMode.config) RouteMode routeMode,
@Default(true) bool autoSetSystemDns,
}) = _NetworkProps;
factory NetworkProps.fromJson(Map<String, Object?>? json) =>
@@ -191,7 +192,7 @@ class ThemeProps with _$ThemeProps {
int? primaryColor,
@Default(defaultPrimaryColors) List<int> primaryColors,
@Default(ThemeMode.dark) ThemeMode themeMode,
@Default(DynamicSchemeVariant.tonalSpot) DynamicSchemeVariant schemeVariant,
@Default(DynamicSchemeVariant.content) DynamicSchemeVariant schemeVariant,
@Default(false) bool pureBlack,
@Default(TextScale()) TextScale textScale,
}) = _ThemeProps;
@@ -211,6 +212,35 @@ class ThemeProps with _$ThemeProps {
}
}
@freezed
class ScriptProps with _$ScriptProps {
const factory ScriptProps({
String? currentId,
@Default([]) List<Script> scripts,
}) = _ScriptProps;
factory ScriptProps.fromJson(Map<String, Object?> json) =>
_$ScriptPropsFromJson(json);
}
extension ScriptPropsExt on ScriptProps {
String? get realId {
final index = scripts.indexWhere((script) => script.id == currentId);
if (index != -1) {
return currentId;
}
return null;
}
Script? get currentScript {
final index = scripts.indexWhere((script) => script.id == currentId);
if (index != -1) {
return scripts[index];
}
return null;
}
}
@freezed
class Config with _$Config {
const factory Config({
@@ -228,6 +258,7 @@ class Config with _$Config {
@Default(defaultProxiesStyle) ProxiesStyle proxiesStyle,
@Default(defaultWindowProps) WindowProps windowProps,
@Default(defaultClashConfig) ClashConfig patchClashConfig,
@Default(ScriptProps()) ScriptProps scriptProps,
}) = _Config;
factory Config.fromJson(Map<String, Object?> json) => _$ConfigFromJson(json);

View File

@@ -5,7 +5,6 @@ import 'package:fl_clash/models/models.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'generated/core.freezed.dart';
part 'generated/core.g.dart';
abstract mixin class AppMessageListener {
@@ -18,10 +17,52 @@ abstract mixin class AppMessageListener {
void onLoaded(String providerName) {}
}
abstract mixin class ServiceMessageListener {
onProtect(Fd fd) {}
// abstract mixin class ServiceMessageListener {
// onProtect(Fd fd) {}
//
// onProcess(ProcessData process) {}
// }
onProcess(ProcessData process) {}
@freezed
class SetupParams with _$SetupParams {
const factory SetupParams({
@JsonKey(name: "config") required Map<String, dynamic> config,
@JsonKey(name: "selected-map") required Map<String, String> selectedMap,
@JsonKey(name: "test-url") required String testUrl,
}) = _SetupParams;
factory SetupParams.fromJson(Map<String, dynamic> json) =>
_$SetupParamsFromJson(json);
}
// extension SetupParamsExt on SetupParams {
// Map<String, dynamic> get json {
// final json = Map<String, dynamic>.from(config);
// json["selected-map"] = selectedMap;
// json["test-url"] = testUrl;
// return json;
// }
// }
@freezed
class UpdateParams with _$UpdateParams {
const factory UpdateParams({
required Tun tun,
@JsonKey(name: 'mixed-port') required int mixedPort,
@JsonKey(name: 'allow-lan') required bool allowLan,
@JsonKey(name: 'find-process-mode')
required FindProcessMode findProcessMode,
required Mode mode,
@JsonKey(name: 'log-level') required LogLevel logLevel,
required bool ipv6,
@JsonKey(name: 'tcp-concurrent') required bool tcpConcurrent,
@JsonKey(name: 'external-controller')
required ExternalControllerStatus externalController,
@JsonKey(name: 'unified-delay') required bool unifiedDelay,
}) = _UpdateParams;
factory UpdateParams.fromJson(Map<String, dynamic> json) =>
_$UpdateParamsFromJson(json);
}
@freezed
@@ -56,32 +97,6 @@ class AndroidVpnOptions with _$AndroidVpnOptions {
_$AndroidVpnOptionsFromJson(json);
}
@freezed
class ConfigExtendedParams with _$ConfigExtendedParams {
const factory ConfigExtendedParams({
@JsonKey(name: "is-patch") required bool isPatch,
@JsonKey(name: "selected-map") required SelectedMap selectedMap,
@JsonKey(name: "override-dns") required bool overrideDns,
@JsonKey(name: "override-rule") required bool overrideRule,
@JsonKey(name: "test-url") required String testUrl,
}) = _ConfigExtendedParams;
factory ConfigExtendedParams.fromJson(Map<String, Object?> json) =>
_$ConfigExtendedParamsFromJson(json);
}
@freezed
class UpdateConfigParams with _$UpdateConfigParams {
const factory UpdateConfigParams({
@JsonKey(name: "profile-id") required String profileId,
required ClashConfig config,
required ConfigExtendedParams params,
}) = _UpdateConfigParams;
factory UpdateConfigParams.fromJson(Map<String, Object?> json) =>
_$UpdateConfigParamsFromJson(json);
}
@freezed
class InitParams with _$InitParams {
const factory InitParams({
@@ -158,37 +173,26 @@ class Now with _$Now {
factory Now.fromJson(Map<String, Object?> json) => _$NowFromJson(json);
}
@freezed
class ProcessData with _$ProcessData {
const factory ProcessData({
required String id,
required Metadata metadata,
}) = _ProcessData;
factory ProcessData.fromJson(Map<String, Object?> json) =>
_$ProcessDataFromJson(json);
}
@freezed
class Fd with _$Fd {
const factory Fd({
required String id,
required int value,
}) = _Fd;
factory Fd.fromJson(Map<String, Object?> json) => _$FdFromJson(json);
}
@freezed
class ProcessMapItem with _$ProcessMapItem {
const factory ProcessMapItem({
required String id,
required String value,
}) = _ProcessMapItem;
factory ProcessMapItem.fromJson(Map<String, Object?> json) =>
_$ProcessMapItemFromJson(json);
}
// @freezed
// class ProcessData with _$ProcessData {
// const factory ProcessData({
// required String id,
// required Metadata metadata,
// }) = _ProcessData;
//
// factory ProcessData.fromJson(Map<String, Object?> json) =>
// _$ProcessDataFromJson(json);
// }
//
// @freezed
// class Fd with _$Fd {
// const factory Fd({
// required String id,
// required int value,
// }) = _Fd;
//
// factory Fd.fromJson(Map<String, Object?> json) => _$FdFromJson(json);
// }
@freezed
class ProviderSubscriptionInfo with _$ProviderSubscriptionInfo {
@@ -231,28 +235,11 @@ class ExternalProvider with _$ExternalProvider {
_$ExternalProviderFromJson(json);
}
@freezed
class TunProps with _$TunProps {
const factory TunProps({
required int fd,
required String gateway,
required String gateway6,
required String portal,
required String portal6,
required String dns,
required String dns6,
}) = _TunProps;
factory TunProps.fromJson(Map<String, Object?> json) =>
_$TunPropsFromJson(json);
}
@freezed
class Action with _$Action {
const factory Action({
required ActionMethod method,
required dynamic data,
@JsonKey(name: "default-value") required dynamic defaultValue,
required String id,
}) = _Action;
@@ -265,8 +252,19 @@ class ActionResult with _$ActionResult {
required ActionMethod method,
required dynamic data,
String? id,
@Default(ResultType.success) ResultType code,
}) = _ActionResult;
factory ActionResult.fromJson(Map<String, Object?> json) =>
_$ActionResultFromJson(json);
}
extension ActionResultExt on ActionResult {
Result get toResult {
if (code == ResultType.success) {
return Result.success(data);
} else {
return Result.error(data);
}
}
}

View File

@@ -17,6 +17,7 @@ final _privateConstructorUsedError = UnsupportedError(
/// @nodoc
mixin _$AppState {
bool get isInit => throw _privateConstructorUsedError;
bool get backBlock => throw _privateConstructorUsedError;
PageLabel get pageLabel => throw _privateConstructorUsedError;
List<Package> get packages => throw _privateConstructorUsedError;
int get sortNum => throw _privateConstructorUsedError;
@@ -34,7 +35,8 @@ mixin _$AppState {
FixedList<Log> get logs => throw _privateConstructorUsedError;
FixedList<Traffic> get traffics => throw _privateConstructorUsedError;
Traffic get totalTraffic => throw _privateConstructorUsedError;
bool get needApply => throw _privateConstructorUsedError;
String get proxiesQuery => throw _privateConstructorUsedError;
bool get realTunEnable => throw _privateConstructorUsedError;
/// Create a copy of AppState
/// with the given fields replaced by the non-null parameter values.
@@ -50,6 +52,7 @@ abstract class $AppStateCopyWith<$Res> {
@useResult
$Res call(
{bool isInit,
bool backBlock,
PageLabel pageLabel,
List<Package> packages,
int sortNum,
@@ -66,7 +69,8 @@ abstract class $AppStateCopyWith<$Res> {
FixedList<Log> logs,
FixedList<Traffic> traffics,
Traffic totalTraffic,
bool needApply});
String proxiesQuery,
bool realTunEnable});
}
/// @nodoc
@@ -85,6 +89,7 @@ class _$AppStateCopyWithImpl<$Res, $Val extends AppState>
@override
$Res call({
Object? isInit = null,
Object? backBlock = null,
Object? pageLabel = null,
Object? packages = null,
Object? sortNum = null,
@@ -101,13 +106,18 @@ class _$AppStateCopyWithImpl<$Res, $Val extends AppState>
Object? logs = null,
Object? traffics = null,
Object? totalTraffic = null,
Object? needApply = null,
Object? proxiesQuery = null,
Object? realTunEnable = null,
}) {
return _then(_value.copyWith(
isInit: null == isInit
? _value.isInit
: isInit // ignore: cast_nullable_to_non_nullable
as bool,
backBlock: null == backBlock
? _value.backBlock
: backBlock // ignore: cast_nullable_to_non_nullable
as bool,
pageLabel: null == pageLabel
? _value.pageLabel
: pageLabel // ignore: cast_nullable_to_non_nullable
@@ -172,9 +182,13 @@ class _$AppStateCopyWithImpl<$Res, $Val extends AppState>
? _value.totalTraffic
: totalTraffic // ignore: cast_nullable_to_non_nullable
as Traffic,
needApply: null == needApply
? _value.needApply
: needApply // ignore: cast_nullable_to_non_nullable
proxiesQuery: null == proxiesQuery
? _value.proxiesQuery
: proxiesQuery // ignore: cast_nullable_to_non_nullable
as String,
realTunEnable: null == realTunEnable
? _value.realTunEnable
: realTunEnable // ignore: cast_nullable_to_non_nullable
as bool,
) as $Val);
}
@@ -190,6 +204,7 @@ abstract class _$$AppStateImplCopyWith<$Res>
@useResult
$Res call(
{bool isInit,
bool backBlock,
PageLabel pageLabel,
List<Package> packages,
int sortNum,
@@ -206,7 +221,8 @@ abstract class _$$AppStateImplCopyWith<$Res>
FixedList<Log> logs,
FixedList<Traffic> traffics,
Traffic totalTraffic,
bool needApply});
String proxiesQuery,
bool realTunEnable});
}
/// @nodoc
@@ -223,6 +239,7 @@ class __$$AppStateImplCopyWithImpl<$Res>
@override
$Res call({
Object? isInit = null,
Object? backBlock = null,
Object? pageLabel = null,
Object? packages = null,
Object? sortNum = null,
@@ -239,13 +256,18 @@ class __$$AppStateImplCopyWithImpl<$Res>
Object? logs = null,
Object? traffics = null,
Object? totalTraffic = null,
Object? needApply = null,
Object? proxiesQuery = null,
Object? realTunEnable = null,
}) {
return _then(_$AppStateImpl(
isInit: null == isInit
? _value.isInit
: isInit // ignore: cast_nullable_to_non_nullable
as bool,
backBlock: null == backBlock
? _value.backBlock
: backBlock // ignore: cast_nullable_to_non_nullable
as bool,
pageLabel: null == pageLabel
? _value.pageLabel
: pageLabel // ignore: cast_nullable_to_non_nullable
@@ -310,9 +332,13 @@ class __$$AppStateImplCopyWithImpl<$Res>
? _value.totalTraffic
: totalTraffic // ignore: cast_nullable_to_non_nullable
as Traffic,
needApply: null == needApply
? _value.needApply
: needApply // ignore: cast_nullable_to_non_nullable
proxiesQuery: null == proxiesQuery
? _value.proxiesQuery
: proxiesQuery // ignore: cast_nullable_to_non_nullable
as String,
realTunEnable: null == realTunEnable
? _value.realTunEnable
: realTunEnable // ignore: cast_nullable_to_non_nullable
as bool,
));
}
@@ -323,6 +349,7 @@ class __$$AppStateImplCopyWithImpl<$Res>
class _$AppStateImpl implements _AppState {
const _$AppStateImpl(
{this.isInit = false,
this.backBlock = false,
this.pageLabel = PageLabel.dashboard,
final List<Package> packages = const [],
this.sortNum = 0,
@@ -339,7 +366,8 @@ class _$AppStateImpl implements _AppState {
required this.logs,
required this.traffics,
required this.totalTraffic,
this.needApply = false})
this.proxiesQuery = "",
this.realTunEnable = false})
: _packages = packages,
_delayMap = delayMap,
_groups = groups,
@@ -350,6 +378,9 @@ class _$AppStateImpl implements _AppState {
final bool isInit;
@override
@JsonKey()
final bool backBlock;
@override
@JsonKey()
final PageLabel pageLabel;
final List<Package> _packages;
@override
@@ -413,11 +444,14 @@ class _$AppStateImpl implements _AppState {
final Traffic totalTraffic;
@override
@JsonKey()
final bool needApply;
final String proxiesQuery;
@override
@JsonKey()
final bool realTunEnable;
@override
String toString() {
return 'AppState(isInit: $isInit, pageLabel: $pageLabel, packages: $packages, sortNum: $sortNum, viewSize: $viewSize, delayMap: $delayMap, groups: $groups, checkIpNum: $checkIpNum, brightness: $brightness, runTime: $runTime, providers: $providers, localIp: $localIp, requests: $requests, version: $version, logs: $logs, traffics: $traffics, totalTraffic: $totalTraffic, needApply: $needApply)';
return 'AppState(isInit: $isInit, backBlock: $backBlock, pageLabel: $pageLabel, packages: $packages, sortNum: $sortNum, viewSize: $viewSize, delayMap: $delayMap, groups: $groups, checkIpNum: $checkIpNum, brightness: $brightness, runTime: $runTime, providers: $providers, localIp: $localIp, requests: $requests, version: $version, logs: $logs, traffics: $traffics, totalTraffic: $totalTraffic, proxiesQuery: $proxiesQuery, realTunEnable: $realTunEnable)';
}
@override
@@ -426,6 +460,8 @@ class _$AppStateImpl implements _AppState {
(other.runtimeType == runtimeType &&
other is _$AppStateImpl &&
(identical(other.isInit, isInit) || other.isInit == isInit) &&
(identical(other.backBlock, backBlock) ||
other.backBlock == backBlock) &&
(identical(other.pageLabel, pageLabel) ||
other.pageLabel == pageLabel) &&
const DeepCollectionEquality().equals(other._packages, _packages) &&
@@ -450,31 +486,36 @@ class _$AppStateImpl implements _AppState {
other.traffics == traffics) &&
(identical(other.totalTraffic, totalTraffic) ||
other.totalTraffic == totalTraffic) &&
(identical(other.needApply, needApply) ||
other.needApply == needApply));
(identical(other.proxiesQuery, proxiesQuery) ||
other.proxiesQuery == proxiesQuery) &&
(identical(other.realTunEnable, realTunEnable) ||
other.realTunEnable == realTunEnable));
}
@override
int get hashCode => Object.hash(
runtimeType,
isInit,
pageLabel,
const DeepCollectionEquality().hash(_packages),
sortNum,
viewSize,
const DeepCollectionEquality().hash(_delayMap),
const DeepCollectionEquality().hash(_groups),
checkIpNum,
brightness,
runTime,
const DeepCollectionEquality().hash(_providers),
localIp,
requests,
version,
logs,
traffics,
totalTraffic,
needApply);
int get hashCode => Object.hashAll([
runtimeType,
isInit,
backBlock,
pageLabel,
const DeepCollectionEquality().hash(_packages),
sortNum,
viewSize,
const DeepCollectionEquality().hash(_delayMap),
const DeepCollectionEquality().hash(_groups),
checkIpNum,
brightness,
runTime,
const DeepCollectionEquality().hash(_providers),
localIp,
requests,
version,
logs,
traffics,
totalTraffic,
proxiesQuery,
realTunEnable
]);
/// Create a copy of AppState
/// with the given fields replaced by the non-null parameter values.
@@ -488,6 +529,7 @@ class _$AppStateImpl implements _AppState {
abstract class _AppState implements AppState {
const factory _AppState(
{final bool isInit,
final bool backBlock,
final PageLabel pageLabel,
final List<Package> packages,
final int sortNum,
@@ -504,11 +546,14 @@ abstract class _AppState implements AppState {
required final FixedList<Log> logs,
required final FixedList<Traffic> traffics,
required final Traffic totalTraffic,
final bool needApply}) = _$AppStateImpl;
final String proxiesQuery,
final bool realTunEnable}) = _$AppStateImpl;
@override
bool get isInit;
@override
bool get backBlock;
@override
PageLabel get pageLabel;
@override
List<Package> get packages;
@@ -541,7 +586,9 @@ abstract class _AppState implements AppState {
@override
Traffic get totalTraffic;
@override
bool get needApply;
String get proxiesQuery;
@override
bool get realTunEnable;
/// Create a copy of AppState
/// with the given fields replaced by the non-null parameter values.

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