Compare commits

..

60 Commits

Author SHA1 Message Date
chen08209
d3991a9360 Update build.yml 2024-06-08 15:09:29 +08:00
chen08209
7ea226ef4a Add one-click update all profiles
Add expire show
2024-06-08 15:04:18 +08:00
chen08209
9bf50e0f3b Optimize delay test2 2024-06-08 12:12:16 +08:00
chen08209
10203b6c87 cache 2024-06-08 12:11:32 +08:00
chen08209
86aaf7ded1 Use system fontFamily 2024-06-07 17:22:55 +08:00
chen08209
bc337c3999 Change go version 2024-06-07 17:20:53 +08:00
chen08209
38b1ce75d8 Temp remove tun mode
(cherry picked from commit d2d9bdab02)
2024-06-07 17:20:12 +08:00
chen08209
a3d75d276c Remove macos in workflow
(cherry picked from commit d0f8444b6d)
2024-06-07 17:20:11 +08:00
chen08209
3c46cd792c Optimize delay test
Add check ip
2024-06-06 16:33:32 +08:00
chen08209
42944b425f Fix tun unable to open 2024-06-06 16:33:32 +08:00
chen08209
4c72877e58 Fix the problem that the download of remote resources failed after GeodataMode was turned on, which caused the application to flash back.
Fix edit profile error
2024-06-06 16:33:32 +08:00
chen08209
81a19631e1 Fix quickStart change proxy error 2024-06-06 16:33:31 +08:00
chen08209
7f6a74b777 add check ip request 2024-06-06 16:33:30 +08:00
chen08209
eb9c3232ec Fix core version 2024-06-05 14:29:38 +08:00
chen08209
e8c9c619bf Fix core version 2024-06-05 14:13:04 +08:00
chen08209
17cd0cf1ed Update file_picker
Add resources page

Optimize more detail
2024-06-05 13:42:33 +08:00
chen08209
925c331498 Add access selected sorted 2024-06-03 14:46:16 +08:00
chen08209
0512d692f2 Fix notification duplicate creation issue
Fix AccessControl click issue
2024-06-03 11:43:41 +08:00
chen08209
844ce8ffb0 Fix Linux unable to open 2024-05-31 22:12:06 +08:00
chen08209
d2588596df Update README.md 3 2024-05-31 15:40:47 +08:00
chen08209
0e999e3deb Create LICENSE
(cherry picked from commit 3ef3190785)
2024-05-31 15:11:45 +08:00
chen08209
8109bbf46c Update README.md 2
(cherry picked from commit b1c763fcfa)
2024-05-31 15:11:45 +08:00
chen08209
32ea45e5ea Update README.md
(cherry picked from commit 9452ffa514)
2024-05-31 15:11:44 +08:00
chen08209
ecce17ad75 optimize checkUpdate 2024-05-31 09:59:18 +08:00
chen08209
96738090cf Fix submit error
(cherry picked from commit fd3040283c)
2024-05-31 09:09:05 +08:00
chen08209
4a8ea37142 add WebDAV
add Auto check updates

Optimize more details
2024-05-30 17:06:53 +08:00
chen08209
a5fdb90da5 optimize delayTest 2024-05-17 19:54:26 +08:00
chen08209
f9722cc761 upgrade flutter version
(cherry picked from commit 9a07c785f2)
2024-05-17 09:35:22 +08:00
chen08209
f01fb2ed1d Update kernel
Add import profile via QR code image
2024-05-15 20:19:50 +08:00
chen08209
74f4481071 Add compatibility mode and adapt clash scheme. 2024-05-11 14:08:13 +08:00
chen08209
9018f512ae Reconstruction application proxy logic 2024-05-07 17:59:11 +08:00
chen08209
265fc4a701 Fix Tab destroy error 2024-05-06 19:03:49 +08:00
chen08209
755974fc9e Optimize repeat healthcheck 2024-05-06 17:15:42 +08:00
chen08209
ba8eab4fc9 Optimize Direct mode ui 2024-05-06 15:26:38 +08:00
chen08209
f5cb46710f Optimize Healthcheck 2024-05-06 14:31:20 +08:00
chen08209
6483e80416 Remove proxies position animation, improve performance
Add Telegram Link
2024-05-06 14:31:19 +08:00
chen08209
535e6dc3a5 Update healthcheck policy 2024-05-06 14:31:19 +08:00
chen08209
ad86c20cfb New Check URLTest 2024-05-05 21:40:12 +08:00
chen08209
665330e17a Fix the problem of invalid auto-selection 2024-05-05 16:13:52 +08:00
chen08209
0eb001e717 New Async UpdateConfig 2024-05-05 03:12:45 +08:00
chen08209
a563991d74 add changeProfileDebounce 2024-05-04 21:51:40 +08:00
chen08209
3e2a30008c Update Workflow 2024-05-04 16:50:37 +08:00
chen08209
ff68d573d6 Fix ChangeProfile block 2024-05-04 16:39:21 +08:00
chen08209
3223fca7ba Fix Release Message Error
(cherry picked from commit aef50fe0e3)
2024-05-04 16:38:03 +08:00
chen08209
006f9127fc Update Selector 2
(cherry picked from commit fc0767ed25)
2024-05-04 16:37:59 +08:00
chen08209
a2709e155c Update Version
(cherry picked from commit dbf1724cca)
2024-05-04 16:37:57 +08:00
chen08209
684fa7b58e Fix Proxies Select Error
(cherry picked from commit 909aa4038e)
2024-05-04 16:37:55 +08:00
chen08209
03b4da54b5 Fix the problem that the proxy group is empty in global mode.
(cherry picked from commit 2d0a7d8d46)
2024-05-04 16:37:54 +08:00
chen08209
a904b55d11 Fix the problem that the proxy group is empty in global mode.
(cherry picked from commit ca96cd1d82)
2024-05-04 16:37:54 +08:00
chen08209
d711935e2e Add ProxyProvider2
(cherry picked from commit 91ab1e5dac)
2024-05-04 16:37:53 +08:00
chen08209
98b1496eff Add ProxyProvider
(cherry picked from commit b3a5f74df8)
2024-05-03 21:28:41 +08:00
chen08209
442c32b6eb Update Version 2024-05-03 15:32:12 +08:00
chen08209
949a2aaac3 Update ProxyGroup Sort 2024-05-03 14:31:10 +08:00
chen08209
c77463f337 Fix Android quickStart VpnService some problems 2024-05-02 00:46:42 +08:00
chen08209
00377d6070 Update version 2024-05-01 23:39:21 +08:00
chen08209
f393b4b3e9 Set Android notification low importance 2024-05-01 23:29:32 +08:00
chen08209
75e6cfde15 Add Telegram in README_zh_CN.md
(cherry picked from commit 8a188a37c9)
2024-05-01 21:52:22 +08:00
chen08209
7bfe5617d9 Add Telegram 2024-05-01 21:49:18 +08:00
chen08209
97cc96c243 Fix the issue that VpnService can't be closed correctly in special cases 2024-05-01 21:29:54 +08:00
chen08209
1821ee2f61 Fix the problem that TileService is not destroyed correctly in some cases
Adjust tab animation defaults
2024-05-01 15:13:09 +08:00
208 changed files with 3963 additions and 18627 deletions

View File

@@ -3,7 +3,7 @@ name: build
on: on:
push: push:
tags: tags:
- 'v*' - '*'
jobs: jobs:
build: build:
@@ -15,38 +15,12 @@ jobs:
os: ubuntu-latest os: ubuntu-latest
- platform: windows - platform: windows
os: windows-latest os: windows-latest
arch: amd64
- platform: linux - platform: linux
os: ubuntu-latest os: ubuntu-latest
arch: amd64
- platform: macos - platform: macos
os: macos-13 os: macos-13
arch: amd64
- platform: macos
os: macos-latest
arch: arm64
steps: steps:
- name: Setup Mingw64
if: startsWith(matrix.platform,'windows')
uses: msys2/setup-msys2@v2
with:
msystem: mingw64
install: mingw-w64-x86_64-gcc
update: true
- name: Set Mingw64 Env
if: startsWith(matrix.platform,'windows')
run: |
echo "${{ runner.temp }}\msys64\mingw64\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
- name: Check Matrix
run: |
echo "Running on ${{ matrix.os }}"
echo "Arch: ${{ runner.arch }}"
gcc --version
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
@@ -72,10 +46,10 @@ jobs:
if: startsWith(matrix.platform,'android') if: startsWith(matrix.platform,'android')
run: | run: |
echo "${{ secrets.KEYSTORE }}" | base64 --decode > android/app/keystore.jks echo "${{ secrets.KEYSTORE }}" | base64 --decode > android/app/keystore.jks
echo "keyAlias=${{ secrets.KEY_ALIAS }}" >> android/local.properties echo "keyAlias=${{ secrets.KEY_ALIAS }}" >> android/local.properties
echo "storePassword=${{ secrets.STORE_PASSWORD }}" >> android/local.properties echo "storePassword=${{ secrets.STORE_PASSWORD }}" >> android/local.properties
echo "keyPassword=${{ secrets.KEY_PASSWORD }}" >> android/local.properties echo "keyPassword=${{ secrets.KEY_PASSWORD }}" >> android/local.properties
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v5 uses: actions/setup-go@v5
@@ -95,19 +69,20 @@ jobs:
run: flutter pub get run: flutter pub get
- name: Setup - name: Setup
run: dart setup.dart ${{ matrix.platform }} ${{ matrix.arch && format('--arch {0}', matrix.arch) }} run: |
dart setup.dart ${{ matrix.platform }}
- name: Upload - name: Upload
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: artifact-${{ matrix.platform }}${{ matrix.arch && format('-{0}', matrix.arch) }} name: artifact-${{ matrix.platform }}
path: ./dist path: ./dist
retention-days: 1 retention-days: 1
overwrite: true overwrite: true
upload-release: upload-release:
if: ${{ !contains(github.ref, '+') }} if: ${{ !endsWith(github.ref, '-debug') }}
permissions: write-all permissions: write-all
needs: [ build ] needs: [ build ]
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -140,18 +115,4 @@ jobs:
uses: softprops/action-gh-release@v2 uses: softprops/action-gh-release@v2
with: with:
files: ./dist/* files: ./dist/*
body_path: './release.md' body_path: './release.md'
- name: Push to fdroid repo
uses: cpina/github-action-push-to-another-repository@v1.7.2
env:
SSH_DEPLOY_KEY: ${{ secrets.SSH_DEPLOY_KEY }}
with:
source-directory: ./dist/
destination-github-username: chen08209
destination-repository-name: FlClash-fdroid-repo
user-name: 'github-actions[bot]'
user-email: 'github-actions[bot]@users.noreply.github.com'
target-branch: action-pr
commit-message: Update from ${{ github.ref_name }}
target-directory: /tmp/

2
.gitmodules vendored
View File

@@ -5,4 +5,4 @@
[submodule "plugins/flutter_distributor"] [submodule "plugins/flutter_distributor"]
path = plugins/flutter_distributor path = plugins/flutter_distributor
url = git@github.com:chen08209/flutter_distributor.git url = git@github.com:chen08209/flutter_distributor.git
branch = FlClash branch = main

View File

@@ -34,14 +34,8 @@ on Mobile:
💡 Based on Material You Design, [Surfboard](https://github.com/getsurfboard/surfboard)-like UI 💡 Based on Material You Design, [Surfboard](https://github.com/getsurfboard/surfboard)-like UI
☁️ Supports data sync via WebDAV
✨ Support subscription link, Dark mode ✨ Support subscription link, Dark mode
## Download
<a href="https://chen08209.github.io/FlClash-fdroid-repo/repo?fingerprint=789D6D32668712EF7672F9E58DEEB15FBD6DCEEC5AE7A4371EA72F2AAE8A12FD"><img alt="Get it on F-Droid" src="snapshots/get-it-on-fdroid.svg" width="200px"/></a> <a href="https://github.com/chen08209/FlClash/releases"><img alt="Get it on GitHub" src="snapshots/get-it-on-github.svg" width="200px"/></a>
## Contact ## Contact
[Telegram](https://t.me/+G-veVtwBOl4wODc1) [Telegram](https://t.me/+G-veVtwBOl4wODc1)

View File

@@ -34,15 +34,8 @@ on Mobile:
💡 基本 Material You 设计, 类[Surfboard](https://github.com/getsurfboard/surfboard)用户界面 💡 基本 Material You 设计, 类[Surfboard](https://github.com/getsurfboard/surfboard)用户界面
☁️ 支持通过WebDAV同步数据
✨ 支持一键导入订阅, 深色模式 ✨ 支持一键导入订阅, 深色模式
## Download
<a href="https://chen08209.github.io/FlClash-fdroid-repo/repo?fingerprint=789D6D32668712EF7672F9E58DEEB15FBD6DCEEC5AE7A4371EA72F2AAE8A12FD"><img alt="Get it on F-Droid" src="snapshots/get-it-on-fdroid.svg" width="200px"/></a> <a href="https://github.com/chen08209/FlClash/releases"><img alt="Get it on GitHub" src="snapshots/get-it-on-github.svg" width="200px"/></a>
## Contact ## Contact
[Telegram](https://t.me/+G-veVtwBOl4wODc1) [Telegram](https://t.me/+G-veVtwBOl4wODc1)

View File

@@ -1,6 +1,9 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<uses-feature
android:name="android.software.leanback"
android:required="false" />
<uses-feature <uses-feature
android:name="android.hardware.touchscreen" android:name="android.hardware.touchscreen"
android:required="false" /> android:required="false" />
@@ -14,19 +17,14 @@
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" /> <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" <uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
tools:ignore="SystemPermissionTypo" /> <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" tools:ignore="QueryAllPackagesPermission"/>
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="QueryAllPackagesPermission" />
<application <application
android:name="${applicationName}" android:name="${applicationName}"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:networkSecurityConfig="@xml/network_security_config" android:networkSecurityConfig="@xml/network_security_config"
android:extractNativeLibs="true" android:label="FlClash">
android:enableOnBackInvokedCallback="true"
android:label="FlClash"
tools:targetApi="tiramisu">
<activity <activity
android:name="com.follow.clash.MainActivity" android:name="com.follow.clash.MainActivity"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
@@ -75,11 +73,9 @@
<service <service
android:name=".services.FlClashTileService" android:name=".services.FlClashTileService"
android:exported="true" android:exported="true"
android:icon="@drawable/ic_stat_name" android:icon="@drawable/tile_icon"
android:foregroundServiceType="specialUse"
android:label="FlClash" android:label="FlClash"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE" android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
>
<intent-filter> <intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE" /> <action android:name="android.service.quicksettings.action.QS_TILE" />
</intent-filter> </intent-filter>
@@ -87,35 +83,11 @@
android:name="android.service.quicksettings.TOGGLEABLE_TILE" android:name="android.service.quicksettings.TOGGLEABLE_TILE"
android:value="true" /> android:value="true" />
</service> </service>
<provider
android:name=".FilesProvider"
android:authorities="${applicationId}.files"
android:exported="true"
android:grantUriPermissions="true"
android:permission="android.permission.MANAGE_DOCUMENTS"
android:process=":background">
<intent-filter>
<action android:name="android.content.action.DOCUMENTS_PROVIDER" />
</intent-filter>
</provider>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
<service <service
android:name=".services.FlClashVpnService" android:name=".services.FlClashVpnService"
android:exported="false" android:exported="false"
android:foregroundServiceType="specialUse" android:foregroundServiceType="specialUse"
android:permission="android.permission.BIND_VPN_SERVICE" android:permission="android.permission.BIND_VPN_SERVICE">
>
<intent-filter> <intent-filter>
<action android:name="android.net.VpnService" /> <action android:name="android.net.VpnService" />
</intent-filter> </intent-filter>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 72 KiB

View File

@@ -1,112 +0,0 @@
package com.follow.clash
import android.database.Cursor
import android.database.MatrixCursor
import android.os.CancellationSignal
import android.os.ParcelFileDescriptor
import android.provider.DocumentsContract.Document
import android.provider.DocumentsContract.Root
import android.provider.DocumentsProvider
import java.io.File
import java.io.FileNotFoundException
class FilesProvider : DocumentsProvider() {
companion object {
private const val DEFAULT_ROOT_ID = "0"
private val DEFAULT_DOCUMENT_COLUMNS = arrayOf(
Document.COLUMN_DOCUMENT_ID,
Document.COLUMN_DISPLAY_NAME,
Document.COLUMN_MIME_TYPE,
Document.COLUMN_FLAGS,
Document.COLUMN_SIZE,
)
private val DEFAULT_ROOT_COLUMNS = arrayOf(
Root.COLUMN_ROOT_ID,
Root.COLUMN_FLAGS,
Root.COLUMN_ICON,
Root.COLUMN_TITLE,
Root.COLUMN_SUMMARY,
Root.COLUMN_DOCUMENT_ID
)
}
override fun onCreate(): Boolean {
return true
}
override fun queryRoots(projection: Array<String>?): Cursor {
return MatrixCursor(projection ?: DEFAULT_ROOT_COLUMNS).apply {
newRow().apply {
add(Root.COLUMN_ROOT_ID, DEFAULT_ROOT_ID)
add(Root.COLUMN_FLAGS, Root.FLAG_LOCAL_ONLY)
add(Root.COLUMN_ICON, R.mipmap.ic_launcher)
add(Root.COLUMN_TITLE, context!!.getString(R.string.fl_clash))
add(Root.COLUMN_SUMMARY, "Data")
add(Root.COLUMN_DOCUMENT_ID, "/")
}
}
}
override fun queryChildDocuments(
parentDocumentId: String,
projection: Array<String>?,
sortOrder: String?
): Cursor {
val result = MatrixCursor(resolveDocumentProjection(projection))
val parentFile = if (parentDocumentId == "/") {
context?.filesDir
} else {
File(parentDocumentId)
} ?: throw FileNotFoundException("Parent directory not found")
parentFile.listFiles()?.forEach { file ->
includeFile(result, file)
}
return result
}
override fun queryDocument(documentId: String, projection: Array<String>?): Cursor {
val result = MatrixCursor(resolveDocumentProjection(projection))
val file = File(documentId)
includeFile(result, file)
return result
}
override fun openDocument(
documentId: String,
mode: String,
signal: CancellationSignal?
): ParcelFileDescriptor {
val file = File(documentId)
val accessMode = ParcelFileDescriptor.parseMode(mode)
return ParcelFileDescriptor.open(file, accessMode)
}
private fun includeFile(result: MatrixCursor, file: File) {
result.newRow().apply {
add(Document.COLUMN_DOCUMENT_ID, file.absolutePath)
add(Document.COLUMN_DISPLAY_NAME, file.name)
add(Document.COLUMN_SIZE, file.length())
add(
Document.COLUMN_FLAGS,
Document.FLAG_SUPPORTS_WRITE or Document.FLAG_SUPPORTS_DELETE
)
add(Document.COLUMN_MIME_TYPE, getDocumentType(file))
}
}
private fun getDocumentType(file: File): String {
return if (file.isDirectory) {
Document.MIME_TYPE_DIR
} else {
"application/octet-stream"
}
}
private fun resolveDocumentProjection(projection: Array<String>?): Array<String> {
return projection ?: DEFAULT_DOCUMENT_COLUMNS
}
}

View File

@@ -1,16 +1,9 @@
package com.follow.clash package com.follow.clash
import android.content.Context
import android.util.Log
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import com.follow.clash.plugins.AppPlugin
import com.follow.clash.plugins.ProxyPlugin
import com.follow.clash.plugins.TilePlugin import com.follow.clash.plugins.TilePlugin
import io.flutter.FlutterInjector
import io.flutter.embedding.engine.FlutterEngine import io.flutter.embedding.engine.FlutterEngine
import io.flutter.embedding.engine.dart.DartExecutor import java.util.Date
import java.util.concurrent.locks.ReentrantLock
import kotlin.concurrent.withLock
enum class RunState { enum class RunState {
START, START,
@@ -18,46 +11,13 @@ enum class RunState {
STOP STOP
} }
class GlobalState {
object GlobalState { companion object {
val runState: MutableLiveData<RunState> = MutableLiveData<RunState>(RunState.STOP)
private val lock = ReentrantLock() var runTime: Date? = null
var flutterEngine: FlutterEngine? = null
val runState: MutableLiveData<RunState> = MutableLiveData<RunState>(RunState.STOP) fun getCurrentTilePlugin(): TilePlugin? =
var flutterEngine: FlutterEngine? = null flutterEngine?.plugins?.get(TilePlugin::class.java) as TilePlugin?
private var serviceEngine: FlutterEngine? = null
fun getCurrentAppPlugin(): AppPlugin? {
val currentEngine = if (flutterEngine != null) flutterEngine else serviceEngine
return currentEngine?.plugins?.get(AppPlugin::class.java) as AppPlugin?
}
fun getCurrentTitlePlugin(): TilePlugin? {
val currentEngine = if (flutterEngine != null) flutterEngine else serviceEngine
return currentEngine?.plugins?.get(TilePlugin::class.java) as TilePlugin?
}
fun destroyServiceEngine() {
serviceEngine?.destroy()
serviceEngine = null
}
fun initServiceEngine(context: Context) {
if (serviceEngine != null) return
lock.withLock {
destroyServiceEngine()
serviceEngine = FlutterEngine(context)
serviceEngine?.plugins?.add(ProxyPlugin())
serviceEngine?.plugins?.add(AppPlugin())
serviceEngine?.plugins?.add(TilePlugin())
val vpnService = DartExecutor.DartEntrypoint(
FlutterInjector.instance().flutterLoader().findAppBundlePath(),
"vpnService"
)
serviceEngine?.dartExecutor?.executeDartEntrypoint(
vpnService,
)
}
} }
} }

View File

@@ -10,6 +10,7 @@ import io.flutter.embedding.engine.FlutterEngine
class MainActivity : FlutterActivity() { class MainActivity : FlutterActivity() {
override fun configureFlutterEngine(flutterEngine: FlutterEngine) { override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
GlobalState.flutterEngine?.destroy()
super.configureFlutterEngine(flutterEngine) super.configureFlutterEngine(flutterEngine)
flutterEngine.plugins.add(AppPlugin()) flutterEngine.plugins.add(AppPlugin())
flutterEngine.plugins.add(ProxyPlugin()) flutterEngine.plugins.add(ProxyPlugin())

View File

@@ -1,18 +1,17 @@
package com.follow.clash.extensions package com.follow.clash.extensions
import java.net.InetSocketAddress
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.system.OsConstants.IPPROTO_TCP import android.system.OsConstants.IPPROTO_TCP
import android.system.OsConstants.IPPROTO_UDP import android.system.OsConstants.IPPROTO_UDP
import android.util.Base64 import android.util.Base64
import java.net.URL
import androidx.core.graphics.drawable.toBitmap import androidx.core.graphics.drawable.toBitmap
import com.follow.clash.models.Metadata import com.follow.clash.models.Metadata
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.net.InetAddress
import java.net.InetSocketAddress
suspend fun Drawable.getBase64(): String { suspend fun Drawable.getBase64(): String {
@@ -30,8 +29,3 @@ fun Metadata.getProtocol(): Int? {
if (network.startsWith("udp")) return IPPROTO_UDP if (network.startsWith("udp")) return IPPROTO_UDP
return null return null
} }
fun String.getInetSocketAddress(): InetSocketAddress {
val url = URL("https://$this")
return InetSocketAddress(InetAddress.getByName(url.host), url.port)
}

View File

@@ -10,9 +10,3 @@ data class AccessControl(
val acceptList: List<String>, val acceptList: List<String>,
val rejectList: List<String>, val rejectList: List<String>,
) )
data class Props(
val accessControl: AccessControl?,
val allowBypass: Boolean?,
val systemProxy: Boolean?,
)

View File

@@ -1,15 +1,11 @@
package com.follow.clash.models package com.follow.clash.models
data class Process(
val id: Int,
val metadata: Metadata,
)
data class Metadata( data class Metadata(
val network: String, val network: String,
val sourceIP: String, val sourceIP: String,
val sourcePort: Int, val sourcePort: Int,
val destinationIP: String, val destinationIP: String,
val destinationPort: Int, val destinationPort: Int,
val remoteDestination: String,
val host: String val host: String
) )

View File

@@ -2,38 +2,31 @@ package com.follow.clash.plugins
import android.Manifest import android.Manifest
import android.app.Activity import android.app.Activity
import android.app.ActivityManager
import android.content.Context import android.content.Context
import android.content.Intent
import android.content.pm.ApplicationInfo import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.net.ConnectivityManager import android.net.ConnectivityManager
import android.os.Build import android.os.Build
import android.widget.Toast import android.widget.Toast
import androidx.core.content.ContextCompat.getSystemService import androidx.annotation.RequiresApi
import androidx.core.content.FileProvider
import androidx.core.content.getSystemService import androidx.core.content.getSystemService
import com.follow.clash.GlobalState
import com.follow.clash.extensions.getBase64 import com.follow.clash.extensions.getBase64
import com.follow.clash.extensions.getProtocol import com.follow.clash.extensions.getProtocol
import com.follow.clash.models.Metadata
import com.follow.clash.models.Package import com.follow.clash.models.Package
import com.follow.clash.models.Process
import com.google.gson.Gson import com.google.gson.Gson
import io.flutter.embedding.engine.plugins.FlutterPlugin import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.activity.ActivityAware import io.flutter.embedding.engine.plugins.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.Result
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.io.File
import java.net.InetSocketAddress import java.net.InetSocketAddress
class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware { class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware {
private var activity: Activity? = null private var activity: Activity? = null
@@ -51,41 +44,31 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
private val iconMap = mutableMapOf<String, String?>() private val iconMap = mutableMapOf<String, String?>()
override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
scope = CoroutineScope(Dispatchers.Default)
context = flutterPluginBinding.applicationContext; context = flutterPluginBinding.applicationContext;
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "app") channel = MethodChannel(flutterPluginBinding.binaryMessenger, "app")
channel.setMethodCallHandler(this) channel.setMethodCallHandler(this)
} }
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
channel.setMethodCallHandler(null) channel.setMethodCallHandler(null)
scope.cancel()
} }
private fun tip(message: String?) { private fun tip(message: String?) {
if (GlobalState.flutterEngine == null) { if (toast != null) {
if (toast != null) { toast!!.cancel()
toast!!.cancel()
}
toast = Toast.makeText(context, message, Toast.LENGTH_SHORT)
toast!!.show()
} }
toast = Toast.makeText(context, message, Toast.LENGTH_SHORT)
toast!!.show()
} }
override fun onMethodCall(call: MethodCall, result: Result) { @RequiresApi(Build.VERSION_CODES.Q)
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) { when (call.method) {
"moveTaskToBack" -> { "moveTaskToBack" -> {
activity?.moveTaskToBack(true) activity?.moveTaskToBack(true)
result.success(true); result.success(true);
} }
"updateExcludeFromRecents" -> {
val value = call.argument<Boolean>("value")
updateExcludeFromRecents(value)
result.success(true);
}
"getPackages" -> { "getPackages" -> {
scope.launch { scope.launch {
result.success(getPackages()) result.success(getPackages())
@@ -95,34 +78,21 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
"getPackageIcon" -> { "getPackageIcon" -> {
scope.launch { scope.launch {
val packageName = call.argument<String>("packageName") val packageName = call.argument<String>("packageName")
if (packageName == null) { if (packageName != null) {
result.success(getPackageIcon(packageName))
} else {
result.success(null) result.success(null)
return@launch
}
val packageIcon = getPackageIcon(packageName)
packageIcon.let {
if (it != null) {
result.success(it)
return@launch
}
if (iconMap["default"] == null) {
iconMap["default"] =
context?.packageManager?.defaultActivityIcon?.getBase64()
}
result.success(iconMap["default"])
return@launch
} }
} }
} }
"resolverProcess" -> { "getPackageName" -> {
val data = call.argument<String>("data") val data = call.argument<String>("data")
val process = val metadata =
if (data != null) Gson().fromJson( if (data != null) Gson().fromJson(
data, data,
Process::class.java Metadata::class.java
) else null ) else null
val metadata = process?.metadata
val protocol = metadata?.getProtocol() val protocol = metadata?.getProtocol()
if (protocol == null) { if (protocol == null) {
result.success(null) result.success(null)
@@ -130,27 +100,17 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
} }
scope.launch { scope.launch {
withContext(Dispatchers.Default) { withContext(Dispatchers.Default) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { if (context == null) result.success(null)
result.success(null) val source = InetSocketAddress(metadata.sourceIP, metadata.sourcePort)
return@withContext val target = InetSocketAddress(
} metadata.host.ifEmpty { metadata.destinationIP },
if (context == null) { metadata.destinationPort
result.success(null) )
return@withContext
}
if (connectivity == null) { if (connectivity == null) {
connectivity = context!!.getSystemService<ConnectivityManager>() connectivity = context!!.getSystemService<ConnectivityManager>()
} }
val src = InetSocketAddress(metadata.sourceIP, metadata.sourcePort) val uid =
val dst = InetSocketAddress( connectivity?.getConnectionOwnerUid(protocol, source, target)
metadata.destinationIP.ifEmpty { metadata.host },
metadata.destinationPort
)
val uid = try {
connectivity?.getConnectionOwnerUid(protocol, src, dst)
} catch (_: Exception) {
null
}
if (uid == null || uid == -1) { if (uid == null || uid == -1) {
result.success(null) result.success(null)
return@withContext return@withContext
@@ -165,12 +125,7 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
val message = call.argument<String>("message") val message = call.argument<String>("message")
tip(message) tip(message)
result.success(true) result.success(true)
}
"openFile" -> {
val path = call.argument<String>("path")!!
openFile(path)
result.success(true)
} }
else -> { else -> {
@@ -179,77 +134,16 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
} }
} }
private fun openFile(path: String) {
context?.let {
val file = File(path)
val uri = FileProvider.getUriForFile(
it,
"${it.packageName}.fileProvider",
file
)
val intent = Intent(Intent.ACTION_VIEW).setDataAndType(
uri,
"text/plain"
)
val flags =
Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION
val resInfoList = it.packageManager.queryIntentActivities(
intent, PackageManager.MATCH_DEFAULT_ONLY
)
for (resolveInfo in resInfoList) {
val packageName = resolveInfo.activityInfo.packageName
it.grantUriPermission(
packageName,
uri,
flags
)
}
try {
activity?.startActivity(intent)
} catch (e: Exception) {
println(e)
}
}
}
private fun updateExcludeFromRecents(value: Boolean?) {
if (context == null) return
val am = getSystemService(context!!, ActivityManager::class.java)
val task = am?.appTasks?.firstOrNull {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
it.taskInfo.taskId == activity?.taskId
} else {
it.taskInfo.id == activity?.taskId
}
}
when (value) {
true -> task?.setExcludeFromRecents(value)
false -> task?.setExcludeFromRecents(value)
null -> task?.setExcludeFromRecents(false)
}
}
private suspend fun getPackageIcon(packageName: String): String? { private suspend fun getPackageIcon(packageName: String): String? {
val packageManager = context?.packageManager val packageManager = context?.packageManager
if (iconMap[packageName] == null) { if (iconMap[packageName] == null) {
iconMap[packageName] = try { iconMap[packageName] = packageManager?.getApplicationIcon(packageName)?.getBase64()
packageManager?.getApplicationIcon(packageName)?.getBase64()
} catch (_: Exception) {
null
}
} }
return iconMap[packageName] return iconMap[packageName]
} }
private suspend fun getPackages(): String { private suspend fun getPackages(): String {
return withContext(Dispatchers.Default) { return withContext(Dispatchers.Default){
val packageManager = context?.packageManager val packageManager = context?.packageManager
val packages: List<Package>? = val packages: List<Package>? =
packageManager?.getInstalledPackages(PackageManager.GET_META_DATA)?.filter { packageManager?.getInstalledPackages(PackageManager.GET_META_DATA)?.filter {
@@ -268,16 +162,13 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
} }
} }
fun requestGc() {
channel.invokeMethod("gc", null)
}
override fun onAttachedToActivity(binding: ActivityPluginBinding) { override fun onAttachedToActivity(binding: ActivityPluginBinding) {
activity = binding.activity; activity = binding.activity;
scope = CoroutineScope(Dispatchers.Default)
} }
override fun onDetachedFromActivityForConfigChanges() { override fun onDetachedFromActivityForConfigChanges() {
activity = null activity = null;
} }
override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
@@ -286,6 +177,7 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
override fun onDetachedFromActivity() { override fun onDetachedFromActivity() {
channel.invokeMethod("exit", null) channel.invokeMethod("exit", null)
activity = null scope.cancel()
activity = null;
} }
} }

View File

@@ -1,6 +1,7 @@
package com.follow.clash.plugins package com.follow.clash.plugins
import android.Manifest import android.Manifest
import android.annotation.SuppressLint
import android.app.Activity import android.app.Activity
import android.content.ComponentName import android.content.ComponentName
import android.content.Context import android.content.Context
@@ -8,14 +9,14 @@ import android.content.Intent
import android.content.ServiceConnection import android.content.ServiceConnection
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.net.VpnService import android.net.VpnService
import android.os.Build import android.os.Build.VERSION
import android.os.Build.VERSION_CODES
import android.os.IBinder import android.os.IBinder
import android.util.Log
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.follow.clash.GlobalState import com.follow.clash.GlobalState
import com.follow.clash.RunState import com.follow.clash.RunState
import com.follow.clash.models.Props import com.follow.clash.models.AccessControl
import com.follow.clash.services.FlClashVpnService import com.follow.clash.services.FlClashVpnService
import com.google.gson.Gson import com.google.gson.Gson
import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.android.FlutterActivity
@@ -24,36 +25,37 @@ import io.flutter.embedding.engine.plugins.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel
import java.util.Date
class ProxyPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware { class ProxyPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware {
private lateinit var flutterMethodChannel: MethodChannel
val VPN_PERMISSION_REQUEST_CODE = 1001 val VPN_PERMISSION_REQUEST_CODE = 1001
val NOTIFICATION_PERMISSION_REQUEST_CODE = 1002 val NOTIFICATION_PERMISSION_REQUEST_CODE = 1002
private lateinit var flutterMethodChannel: MethodChannel
private var activity: Activity? = null private var activity: Activity? = null
private var context: Context? = null private var context: Context? = null
private var flClashVpnService: FlClashVpnService? = null private var flClashVpnService: FlClashVpnService? = null
private var port: Int = 7890 private var isBound = false
private var props: Props? = null private var port: Int? = null
private var isBlockNotification: Boolean = false private var accessControl: AccessControl? = null
private var isStart: Boolean = false private lateinit var title: String
private lateinit var content: String
private val connection = object : ServiceConnection { private val connection = object : ServiceConnection {
override fun onServiceConnected(className: ComponentName, service: IBinder) { override fun onServiceConnected(className: ComponentName, service: IBinder) {
val binder = service as FlClashVpnService.LocalBinder val binder = service as FlClashVpnService.LocalBinder
flClashVpnService = binder.getService() flClashVpnService = binder.getService()
if (isStart) { port?.let { startVpn(it) }
startVpn() isBound = true
} else {
flClashVpnService?.initServiceEngine()
}
} }
override fun onServiceDisconnected(arg: ComponentName) { override fun onServiceDisconnected(arg0: ComponentName) {
flClashVpnService = null flClashVpnService = null
isBound = false
} }
} }
@@ -68,29 +70,21 @@ class ProxyPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAwar
} }
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) = when (call.method) { override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) = when (call.method) {
"initService" -> { "StartProxy" -> {
isStart = false port = call.argument<Int>("port")
initService()
requestNotificationsPermission()
result.success(true)
}
"startProxy" -> {
isStart = true
port = call.argument<Int>("port")!!
val args = call.argument<String>("args") val args = call.argument<String>("args")
props = accessControl =
if (args != null) Gson().fromJson(args, Props::class.java) else null if (args != null) Gson().fromJson(args, AccessControl::class.java) else null
startVpn() handleStartVpn()
result.success(true) result.success(true)
} }
"stopProxy" -> { "StopProxy" -> {
stopVpn() stopVpn()
result.success(true) result.success(true)
} }
"setProtect" -> { "SetProtect" -> {
val fd = call.argument<Int>("fd") val fd = call.argument<Int>("fd")
if (fd != null) { if (fd != null) {
flClashVpnService?.protect(fd) flClashVpnService?.protect(fd)
@@ -100,10 +94,14 @@ class ProxyPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAwar
} }
} }
"GetRunTimeStamp" -> {
result.success(GlobalState.runTime?.time)
}
"startForeground" -> { "startForeground" -> {
val title = call.argument<String>("title") as String title = call.argument<String>("title") as String
val content = call.argument<String>("content") as String content = call.argument<String>("content") as String
startForeground(title, content) requestNotificationsPermission()
result.success(true) result.success(true)
} }
@@ -112,47 +110,65 @@ class ProxyPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAwar
} }
} }
private fun initService() { private fun handleStartVpn() {
val intent = VpnService.prepare(context) val intent = VpnService.prepare(context)
if (intent != null) { if (intent != null) {
activity?.startActivityForResult(intent, VPN_PERMISSION_REQUEST_CODE) activity?.startActivityForResult(intent, VPN_PERMISSION_REQUEST_CODE)
} else { } else {
if (flClashVpnService != null) { bindService()
flClashVpnService!!.initServiceEngine()
} else {
bindService()
}
} }
} }
private fun startVpn() { private fun startVpn(port: Int) {
if (flClashVpnService == null) { if (GlobalState.runState.value == RunState.START) return;
bindService() flClashVpnService?.start(port, accessControl)
return
}
if (GlobalState.runState.value == RunState.START) return
GlobalState.runState.value = RunState.START GlobalState.runState.value = RunState.START
val intent = VpnService.prepare(context) GlobalState.runTime = Date()
if (intent != null) { startAfter()
stopVpn()
return
}
val fd = flClashVpnService?.start(port, props)
flutterMethodChannel.invokeMethod("started", fd)
} }
private fun stopVpn() { private fun stopVpn() {
if (GlobalState.runState.value == RunState.STOP) return if (GlobalState.runState.value == RunState.STOP) return
GlobalState.runState.value = RunState.STOP
flClashVpnService?.stop() flClashVpnService?.stop()
GlobalState.destroyServiceEngine() unbindService()
GlobalState.runState.value = RunState.STOP;
GlobalState.runTime = null;
} }
private fun startForeground(title: String, content: String) { @SuppressLint("ForegroundServiceType")
private fun startForeground() {
if (GlobalState.runState.value != RunState.START) return if (GlobalState.runState.value != RunState.START) return
flClashVpnService?.startForeground(title, content) flClashVpnService?.startForeground(title, content)
} }
private fun requestNotificationsPermission() {
if (VERSION.SDK_INT >= VERSION_CODES.TIRAMISU) {
val permission = context?.let {
ContextCompat.checkSelfPermission(
it,
Manifest.permission.POST_NOTIFICATIONS
)
}
if (permission == PackageManager.PERMISSION_GRANTED) {
startForeground()
} else {
activity?.let {
ActivityCompat.requestPermissions(
it,
arrayOf(Manifest.permission.POST_NOTIFICATIONS),
NOTIFICATION_PERMISSION_REQUEST_CODE
)
}
}
} else {
startForeground()
}
}
private fun startAfter() {
flutterMethodChannel.invokeMethod("startAfter", flClashVpnService?.fd)
}
override fun onAttachedToActivity(binding: ActivityPluginBinding) { override fun onAttachedToActivity(binding: ActivityPluginBinding) {
activity = binding.activity activity = binding.activity
binding.addActivityResultListener(::onActivityResult) binding.addActivityResultListener(::onActivityResult)
@@ -167,7 +183,7 @@ class ProxyPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAwar
stopVpn() stopVpn()
} }
} }
return true return true;
} }
private fun onRequestPermissionsResultListener( private fun onRequestPermissionsResultListener(
@@ -176,33 +192,16 @@ class ProxyPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAwar
grantResults: IntArray grantResults: IntArray
): Boolean { ): Boolean {
if (requestCode == NOTIFICATION_PERMISSION_REQUEST_CODE) { if (requestCode == NOTIFICATION_PERMISSION_REQUEST_CODE) {
isBlockNotification = true if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
startForeground()
}
} }
return false return true;
} }
private fun requestNotificationsPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
val permission = context?.let {
ContextCompat.checkSelfPermission(
it,
Manifest.permission.POST_NOTIFICATIONS
)
}
if (permission != PackageManager.PERMISSION_GRANTED) {
if (isBlockNotification) return
if (activity == null) return
ActivityCompat.requestPermissions(
activity!!,
arrayOf(Manifest.permission.POST_NOTIFICATIONS),
NOTIFICATION_PERMISSION_REQUEST_CODE
)
}
}
}
override fun onDetachedFromActivityForConfigChanges() { override fun onDetachedFromActivityForConfigChanges() {
activity = null activity = null;
} }
override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
@@ -210,6 +209,7 @@ class ProxyPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAwar
} }
override fun onDetachedFromActivity() { override fun onDetachedFromActivity() {
stopVpn()
activity = null activity = null
} }
@@ -217,4 +217,11 @@ class ProxyPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAwar
val intent = Intent(context, FlClashVpnService::class.java) val intent = Intent(context, FlClashVpnService::class.java)
context?.bindService(intent, connection, Context.BIND_AUTO_CREATE) context?.bindService(intent, connection, Context.BIND_AUTO_CREATE)
} }
private fun unbindService() {
if (isBound) {
context?.unbindService(connection)
isBound = false
}
}
} }

View File

@@ -1,4 +1,3 @@
package com.follow.clash.plugins package com.follow.clash.plugins
import io.flutter.embedding.engine.plugins.FlutterPlugin import io.flutter.embedding.engine.plugins.FlutterPlugin

View File

@@ -1,9 +1,9 @@
package com.follow.clash.services package com.follow.clash.services
import android.annotation.SuppressLint
import android.app.PendingIntent import android.app.PendingIntent
import android.content.Intent import android.content.Intent
import android.os.Build import android.os.Build
import android.os.IBinder
import android.service.quicksettings.Tile import android.service.quicksettings.Tile
import android.service.quicksettings.TileService import android.service.quicksettings.TileService
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
@@ -11,6 +11,12 @@ import androidx.lifecycle.Observer
import com.follow.clash.GlobalState import com.follow.clash.GlobalState
import com.follow.clash.RunState import com.follow.clash.RunState
import com.follow.clash.TempActivity import com.follow.clash.TempActivity
import com.follow.clash.plugins.AppPlugin
import com.follow.clash.plugins.ProxyPlugin
import com.follow.clash.plugins.TilePlugin
import io.flutter.FlutterInjector
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.embedding.engine.dart.DartExecutor
@RequiresApi(Build.VERSION_CODES.N) @RequiresApi(Build.VERSION_CODES.N)
@@ -37,45 +43,54 @@ class FlClashTileService : TileService() {
GlobalState.runState.observeForever(observer) GlobalState.runState.observeForever(observer)
} }
@SuppressLint("StartActivityAndCollapseDeprecated")
private fun activityTransfer() { private fun activityTransfer() {
val intent = Intent(this, TempActivity::class.java) val intent = Intent(this, TempActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK) intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
val pendingIntent = if (Build.VERSION.SDK_INT >= 31) { if (Build.VERSION.SDK_INT >= 34) {
PendingIntent.getActivity( val pendingIntent = PendingIntent.getActivity(
this, this,
0, 0,
intent, intent,
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT PendingIntent.FLAG_IMMUTABLE
) )
} else {
PendingIntent.getActivity(
this,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT
)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
startActivityAndCollapse(pendingIntent) startActivityAndCollapse(pendingIntent)
} else { } else {
startActivityAndCollapse(intent) startActivityAndCollapse(intent)
} }
} }
private var flutterEngine: FlutterEngine? = null;
private fun initFlutterEngine() {
flutterEngine = FlutterEngine(this)
flutterEngine?.plugins?.add(ProxyPlugin())
flutterEngine?.plugins?.add(TilePlugin())
flutterEngine?.plugins?.add(AppPlugin())
GlobalState.flutterEngine = flutterEngine
if (flutterEngine?.dartExecutor?.isExecutingDart != true) {
val vpnService = DartExecutor.DartEntrypoint(
FlutterInjector.instance().flutterLoader().findAppBundlePath(),
"vpnService"
)
flutterEngine?.dartExecutor?.executeDartEntrypoint(vpnService)
}
}
override fun onClick() { override fun onClick() {
super.onClick() super.onClick()
activityTransfer() activityTransfer()
val currentTilePlugin = GlobalState.getCurrentTilePlugin()
if (GlobalState.runState.value == RunState.STOP) { if (GlobalState.runState.value == RunState.STOP) {
GlobalState.runState.value = RunState.PENDING GlobalState.runState.value = RunState.PENDING
val titlePlugin = GlobalState.getCurrentTitlePlugin() if(currentTilePlugin == null){
if (titlePlugin != null) { initFlutterEngine()
titlePlugin.handleStart() }else{
} else { currentTilePlugin.handleStart()
GlobalState.initServiceEngine(applicationContext)
} }
} else if (GlobalState.runState.value == RunState.START) { } else if(GlobalState.runState.value == RunState.START){
GlobalState.runState.value = RunState.PENDING GlobalState.runState.value = RunState.PENDING
GlobalState.getCurrentTitlePlugin()?.handleStop() currentTilePlugin?.handleStop()
} }
} }

View File

@@ -1,9 +1,10 @@
package com.follow.clash.services package com.follow.clash.services
import android.app.Notification.FOREGROUND_SERVICE_IMMEDIATE import android.annotation.SuppressLint
import android.app.NotificationChannel import android.app.NotificationChannel
import android.app.NotificationManager import android.app.NotificationManager
import android.app.PendingIntent import android.app.PendingIntent
import android.app.Service
import android.content.Intent import android.content.Intent
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE
import android.net.ProxyInfo import android.net.ProxyInfo
@@ -11,23 +12,22 @@ import android.net.VpnService
import android.os.Binder import android.os.Binder
import android.os.Build import android.os.Build
import android.os.IBinder import android.os.IBinder
import android.os.Parcel
import android.os.RemoteException
import android.util.Log
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE
import androidx.core.graphics.drawable.IconCompat
import com.follow.clash.GlobalState import com.follow.clash.GlobalState
import com.follow.clash.MainActivity import com.follow.clash.MainActivity
import com.follow.clash.R import com.follow.clash.models.AccessControl
import com.follow.clash.models.AccessControlMode import com.follow.clash.models.AccessControlMode
import com.follow.clash.models.Props
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@SuppressLint("WrongConstant")
class FlClashVpnService : VpnService() { class FlClashVpnService : VpnService() {
private val CHANNEL = "FlClash" private val CHANNEL = "FlClash"
var fd: Int? = null
private val notificationId: Int = 1 private val notificationId: Int = 1
private val passList = listOf( private val passList = listOf(
@@ -50,17 +50,16 @@ class FlClashVpnService : VpnService() {
"192.168.*" "192.168.*"
) )
override fun onCreate() { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
super.onCreate() return START_STICKY
initServiceEngine()
} }
fun start(port: Int, props: Props?): Int? { fun start(port: Int, accessControl: AccessControl?) {
return with(Builder()) { fd = with(Builder()) {
addAddress("172.16.0.1", 30) addAddress("172.16.0.1", 30)
setMtu(9000) setMtu(9000)
addRoute("0.0.0.0", 0) addRoute("0.0.0.0", 0)
props?.accessControl?.let { accessControl -> if (accessControl != null) {
when (accessControl.mode) { when (accessControl.mode) {
AccessControlMode.acceptSelected -> { AccessControlMode.acceptSelected -> {
(accessControl.acceptList + packageName).forEach { (accessControl.acceptList + packageName).forEach {
@@ -81,10 +80,8 @@ class FlClashVpnService : VpnService() {
if (Build.VERSION.SDK_INT >= 29) { if (Build.VERSION.SDK_INT >= 29) {
setMetered(false) setMetered(false)
} }
if (props?.allowBypass == true) { allowBypass()
allowBypass() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && props?.systemProxy == true) {
setHttpProxy( setHttpProxy(
ProxyInfo.buildDirectProxy( ProxyInfo.buildDirectProxy(
"127.0.0.1", "127.0.0.1",
@@ -102,10 +99,11 @@ class FlClashVpnService : VpnService() {
stopForeground() stopForeground()
} }
private val notificationBuilder: NotificationCompat.Builder by lazy {
private val notificationBuilder by lazy {
val intent = Intent(this, MainActivity::class.java) val intent = Intent(this, MainActivity::class.java)
val pendingIntent = if (Build.VERSION.SDK_INT >= 31) { val pendingIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
PendingIntent.getActivity( PendingIntent.getActivity(
this, this,
0, 0,
@@ -120,44 +118,35 @@ class FlClashVpnService : VpnService() {
PendingIntent.FLAG_UPDATE_CURRENT PendingIntent.FLAG_UPDATE_CURRENT
) )
} }
val icon = IconCompat.createWithResource(this, this.applicationInfo.icon)
with(NotificationCompat.Builder(this, CHANNEL)) { with(NotificationCompat.Builder(this, CHANNEL)) {
setSmallIcon(R.drawable.ic_stat_name) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
setSmallIcon(icon)
}
setContentTitle("FlClash") setContentTitle("FlClash")
foregroundServiceBehavior = FOREGROUND_SERVICE_IMMEDIATE
setContentIntent(pendingIntent) setContentIntent(pendingIntent)
setCategory(NotificationCompat.CATEGORY_SERVICE) setCategory(NotificationCompat.CATEGORY_SERVICE)
priority = NotificationCompat.PRIORITY_MIN priority = NotificationCompat.PRIORITY_LOW
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
foregroundServiceBehavior = FOREGROUND_SERVICE_IMMEDIATE
}
setOngoing(true) setOngoing(true)
setShowWhen(false) setShowWhen(false)
setOnlyAlertOnce(true) setOnlyAlertOnce(true)
setAutoCancel(true)
} }
} }
fun initServiceEngine() { @SuppressLint("ForegroundServiceType", "WrongConstant")
GlobalState.initServiceEngine(applicationContext)
}
override fun onTrimMemory(level: Int) {
super.onTrimMemory(level)
GlobalState.getCurrentAppPlugin()?.requestGc()
}
fun startForeground(title: String, content: String) { fun startForeground(title: String, content: String) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel =
NotificationChannel(CHANNEL, "FlClash", NotificationManager.IMPORTANCE_LOW)
val manager = getSystemService(NotificationManager::class.java) val manager = getSystemService(NotificationManager::class.java)
var channel = manager?.getNotificationChannel(CHANNEL) manager.createNotificationChannel(channel)
if (channel == null) {
channel =
NotificationChannel(CHANNEL, "FlClash", NotificationManager.IMPORTANCE_LOW)
manager?.createNotificationChannel(channel)
}
} }
val notification = val notification =
notificationBuilder.setContentTitle(title).setContentText(content).build() notificationBuilder.setContentTitle(title).setContentText(content).build()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
startForeground(notificationId, notification, FOREGROUND_SERVICE_TYPE_SPECIAL_USE) startForeground(notificationId, notification, FOREGROUND_SERVICE_TYPE_SPECIAL_USE)
} else { } else {
startForeground(notificationId, notification) startForeground(notificationId, notification)
@@ -166,7 +155,7 @@ class FlClashVpnService : VpnService() {
private fun stopForeground() { private fun stopForeground() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
stopForeground(STOP_FOREGROUND_REMOVE) stopForeground(Service.STOP_FOREGROUND_REMOVE)
} }
} }
@@ -174,28 +163,14 @@ class FlClashVpnService : VpnService() {
inner class LocalBinder : Binder() { inner class LocalBinder : Binder() {
fun getService(): FlClashVpnService = this@FlClashVpnService fun getService(): FlClashVpnService = this@FlClashVpnService
override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean {
try {
val isSuccess = super.onTransact(code, data, reply, flags)
if (!isSuccess) {
CoroutineScope(Dispatchers.Main).launch {
GlobalState.getCurrentTitlePlugin()?.handleStop()
}
}
return isSuccess
} catch (e: RemoteException) {
throw e
}
}
} }
override fun onBind(intent: Intent): IBinder { override fun onBind(intent: Intent): IBinder {
return binder return binder
} }
override fun onUnbind(intent: Intent?): Boolean { override fun onUnbind(intent: Intent?): Boolean {
GlobalState.getCurrentTilePlugin()?.handleStop();
return super.onUnbind(intent) return super.onUnbind(intent)
} }

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

@@ -1,25 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="240"
android:viewportHeight="240">
<group android:scaleX="0.924"
android:scaleY="0.924"
android:translateX="9.12"
android:translateY="9.12">
<group android:scaleX="0.63461536"
android:scaleY="0.63461536"
android:translateX="45.96154"
android:translateY="43.846153">
<path
android:pathData="M60.65,89.6L154.18,35.6A18,18 107.59,0 1,178.77 42.19L178.77,42.19A18,18 107.59,0 1,172.18 66.78L78.65,120.78A18,18 106.67,0 1,54.06 114.19L54.06,114.19A18,18 106.67,0 1,60.65 89.6z"
android:fillColor="#6666FB"/>
<path
android:pathData="M84.65,131.17L131.42,104.17A18,18 107.83,0 1,156 110.76L156,110.76A18,18 107.83,0 1,149.42 135.35L102.65,162.35A18,18 106.67,0 1,78.06 155.76L78.06,155.76A18,18 106.67,0 1,84.65 131.17z"
android:fillColor="#336AB6"/>
<path
android:pathData="M108.65,172.74L108.65,172.74A18,18 116.03,0 1,133.24 179.33L133.24,179.33A18,18 116.03,0 1,126.65 203.92L126.65,203.92A18,18 116.03,0 1,102.06 197.33L102.06,197.33A18,18 116.03,0 1,108.65 172.74z"
android:fillColor="#5CA8E9"/>
</group>
</group>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/> <background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/> <foreground android:drawable="@mipmap/ic_launcher_foreground"/>
<monochrome android:drawable="@drawable/ic_launcher_foreground" /> <monochrome android:drawable="@mipmap/ic_launcher_foreground" />
</adaptive-icon> </adaptive-icon>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/> <background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/> <foreground android:drawable="@mipmap/ic_launcher_foreground"/>
<monochrome android:drawable="@drawable/ic_launcher_foreground" /> <monochrome android:drawable="@mipmap/ic_launcher_foreground" />
</adaptive-icon> </adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 886 B

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -6,7 +6,7 @@
<item name="android:windowDrawsSystemBarBackgrounds">false</item> <item name="android:windowDrawsSystemBarBackgrounds">false</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item> <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
<item name="android:windowSplashScreenBackground" tools:targetApi="s">#121212</item> <item name="android:windowSplashScreenBackground" tools:targetApi="s">#121212</item>
<item name="android:windowSplashScreenAnimatedIcon" tools:targetApi="s">@drawable/ic_launcher_foreground</item> <item name="android:windowSplashScreenAnimatedIcon" tools:targetApi="s">@mipmap/ic_launcher_foreground</item>
<item name="postSplashScreenTheme">@style/NormalTheme</item> <item name="postSplashScreenTheme">@style/NormalTheme</item>
</style> </style>
</resources> </resources>

View File

@@ -4,6 +4,7 @@
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar"> <style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when <!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame --> the Flutter engine draws its first frame -->
<item name="android:windowBackground">@mipmap/ic_launcher_foreground</item>
</style> </style>
<!-- Theme applied to the Android Window as soon as the process has started. <!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your This theme determines the color of the Android Window while your

View File

@@ -6,7 +6,7 @@
<item name="android:windowDrawsSystemBarBackgrounds">false</item> <item name="android:windowDrawsSystemBarBackgrounds">false</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item> <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
<item name="android:windowSplashScreenBackground" tools:targetApi="s">@color/ic_launcher_background</item> <item name="android:windowSplashScreenBackground" tools:targetApi="s">@color/ic_launcher_background</item>
<item name="android:windowSplashScreenAnimatedIcon" tools:targetApi="s">@drawable/ic_launcher_foreground</item> <item name="android:windowSplashScreenAnimatedIcon" tools:targetApi="s">@mipmap/ic_launcher_foreground</item>
<item name="postSplashScreenTheme">@style/NormalTheme</item> <item name="postSplashScreenTheme">@style/NormalTheme</item>
</style> </style>
</resources> </resources>

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<color name="ic_launcher_background">#FAFAFA</color> <color name="ic_launcher_background">#EFEFEF</color>
</resources> </resources>

View File

@@ -4,6 +4,7 @@
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar"> <style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when <!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame --> the Flutter engine draws its first frame -->
<item name="android:windowBackground">@mipmap/ic_launcher_foreground</item>
</style> </style>
<!-- Theme applied to the Android Window as soon as the process has started. <!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your This theme determines the color of the Android Window while your

View File

@@ -1,6 +0,0 @@
<paths>
<files-path
name="files"
path="."/>
</paths>

View File

@@ -7,8 +7,4 @@
<certificates src="user" /> <certificates src="user" />
</trust-anchors> </trust-anchors>
</base-config> </base-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">localhost</domain>
<domain includeSubdomains="true">127.0.0.1</domain>
</domain-config>
</network-security-config> </network-security-config>

Binary file not shown.

BIN
assets/images/app_icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

View File

@@ -2,20 +2,19 @@ package main
import "C" import "C"
import ( import (
"github.com/metacubex/mihomo/adapter"
"github.com/metacubex/mihomo/adapter/inbound" "github.com/metacubex/mihomo/adapter/inbound"
"github.com/metacubex/mihomo/adapter/outboundgroup"
ap "github.com/metacubex/mihomo/adapter/provider" ap "github.com/metacubex/mihomo/adapter/provider"
"github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/process"
"github.com/metacubex/mihomo/component/resolver" "github.com/metacubex/mihomo/component/resolver"
"github.com/metacubex/mihomo/config" "github.com/metacubex/mihomo/config"
"github.com/metacubex/mihomo/constant" "github.com/metacubex/mihomo/constant/provider"
"github.com/metacubex/mihomo/hub" "github.com/metacubex/mihomo/dns"
"github.com/metacubex/mihomo/hub/executor" "github.com/metacubex/mihomo/hub/executor"
"github.com/metacubex/mihomo/hub/route"
"github.com/metacubex/mihomo/listener" "github.com/metacubex/mihomo/listener"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
"github.com/metacubex/mihomo/tunnel" "github.com/metacubex/mihomo/tunnel"
"math"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
@@ -61,17 +60,11 @@ type ruleProviderSchema struct {
Interval int `provider:"interval,omitempty"` Interval int `provider:"interval,omitempty"`
} }
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"`
}
type GenerateConfigParams struct { type GenerateConfigParams struct {
ProfilePath *string `json:"profile-path"` ProfilePath *string `json:"profile-path"`
Config config.RawConfig `json:"config" ` Config *config.RawConfig `json:"config" `
Params ConfigExtendedParams `json:"params"` IsPatch *bool `json:"is-patch"`
IsCompatible *bool `json:"is-compatible"`
} }
type ChangeProxyParams struct { type ChangeProxyParams struct {
@@ -84,8 +77,20 @@ type TestDelayParams struct {
Timeout int64 `json:"timeout"` Timeout int64 `json:"timeout"`
} }
type ProcessMapItem struct { type Delay struct {
Id int64 `json:"id"` Name string `json:"name"`
Value int32 `json:"value"`
}
type Process struct {
Uid uint32 `json:"uid"`
Network string `json:"network"`
Source string `json:"source"`
Target string `json:"target"`
}
type Now struct {
Name string `json:"name"`
Value string `json:"value"` Value string `json:"value"`
} }
@@ -163,9 +168,9 @@ func getRawConfigWithPath(path *string) *config.RawConfig {
} }
} }
func decorationConfig(profilePath *string, cfg config.RawConfig) *config.RawConfig { func decorationConfig(profilePath *string, cfg config.RawConfig, compatible bool) *config.RawConfig {
prof := getRawConfigWithPath(profilePath) prof := getRawConfigWithPath(profilePath)
overwriteConfig(prof, cfg) overwriteConfig(prof, cfg, compatible)
return prof return prof
} }
@@ -315,39 +320,35 @@ func generateProxyGroupAndRule(proxyGroup *[]map[string]any, rule *[]string) {
*rule = computedRule *rule = computedRule
} }
func overwriteConfig(targetConfig *config.RawConfig, patchConfig config.RawConfig) { func overwriteConfig(targetConfig *config.RawConfig, patchConfig config.RawConfig, compatible bool) {
targetConfig.ExternalController = patchConfig.ExternalController targetConfig.ExternalController = ""
targetConfig.ExternalUI = "" targetConfig.ExternalUI = ""
targetConfig.Interface = "" targetConfig.Interface = ""
targetConfig.ExternalUIURL = "" targetConfig.ExternalUIURL = ""
targetConfig.TCPConcurrent = patchConfig.TCPConcurrent targetConfig.GeodataMode = false
targetConfig.UnifiedDelay = patchConfig.UnifiedDelay //targetConfig.IPv6 = patchConfig.IPv6
//targetConfig.GeodataMode = false
targetConfig.IPv6 = patchConfig.IPv6
targetConfig.LogLevel = patchConfig.LogLevel targetConfig.LogLevel = patchConfig.LogLevel
targetConfig.Port = 0 targetConfig.Port = 0
targetConfig.SocksPort = 0 targetConfig.SocksPort = 0
targetConfig.MixedPort = patchConfig.MixedPort targetConfig.MixedPort = patchConfig.MixedPort
targetConfig.FindProcessMode = patchConfig.FindProcessMode targetConfig.FindProcessMode = process.FindProcessAlways
targetConfig.AllowLan = patchConfig.AllowLan targetConfig.AllowLan = patchConfig.AllowLan
targetConfig.Mode = patchConfig.Mode targetConfig.Mode = patchConfig.Mode
targetConfig.Tun.Enable = patchConfig.Tun.Enable targetConfig.Tun.Enable = patchConfig.Tun.Enable
targetConfig.Tun.Device = patchConfig.Tun.Device targetConfig.Tun.Device = patchConfig.Tun.Device
targetConfig.Tun.DNSHijack = patchConfig.Tun.DNSHijack targetConfig.Tun.DNSHijack = patchConfig.Tun.DNSHijack
targetConfig.Tun.Stack = patchConfig.Tun.Stack targetConfig.Tun.Stack = patchConfig.Tun.Stack
targetConfig.GeodataLoader = patchConfig.GeodataLoader targetConfig.GeodataLoader = "standard"
targetConfig.Profile.StoreSelected = false targetConfig.Profile.StoreSelected = false
targetConfig.GeoXUrl = patchConfig.GeoXUrl
targetConfig.GlobalUA = patchConfig.GlobalUA
if targetConfig.DNS.Enable == false { if targetConfig.DNS.Enable == false {
targetConfig.DNS = patchConfig.DNS targetConfig.DNS = patchConfig.DNS
} }
//if runtime.GOOS == "android" { if runtime.GOOS == "android" {
// targetConfig.DNS.NameServer = append(targetConfig.DNS.NameServer, "dhcp://"+dns.SystemDNSPlaceholder) targetConfig.DNS.NameServer = append(targetConfig.DNS.NameServer, "dhcp://"+dns.SystemDNSPlaceholder)
//} else if runtime.GOOS == "windows" { } else if runtime.GOOS == "windows" {
// targetConfig.DNS.NameServer = append(targetConfig.DNS.NameServer, dns.SystemDNSPlaceholder) targetConfig.DNS.NameServer = append(targetConfig.DNS.NameServer, dns.SystemDNSPlaceholder)
//} }
if configParams.IsCompatible == false { if compatible == false {
targetConfig.ProxyProvider = make(map[string]map[string]any) targetConfig.ProxyProvider = make(map[string]map[string]any)
targetConfig.RuleProvider = make(map[string]map[string]any) targetConfig.RuleProvider = make(map[string]map[string]any)
generateProxyGroupAndRule(&targetConfig.ProxyGroup, &targetConfig.Rule) generateProxyGroupAndRule(&targetConfig.ProxyGroup, &targetConfig.Rule)
@@ -356,17 +357,14 @@ func overwriteConfig(targetConfig *config.RawConfig, patchConfig config.RawConfi
func patchConfig(general *config.General) { func patchConfig(general *config.General) {
log.Infoln("[Apply] patch") log.Infoln("[Apply] patch")
route.ReStartServer(general.ExternalController)
listener.SetAllowLan(general.AllowLan) listener.SetAllowLan(general.AllowLan)
inbound.SetSkipAuthPrefixes(general.SkipAuthPrefixes) inbound.SetSkipAuthPrefixes(general.SkipAuthPrefixes)
inbound.SetAllowedIPs(general.LanAllowedIPs) inbound.SetAllowedIPs(general.LanAllowedIPs)
inbound.SetDisAllowedIPs(general.LanDisAllowedIPs) inbound.SetDisAllowedIPs(general.LanDisAllowedIPs)
listener.SetBindAddress(general.BindAddress) listener.SetBindAddress(general.BindAddress)
tunnel.SetSniffing(general.Sniffing) tunnel.SetSniffing(general.Sniffing)
tunnel.SetFindProcessMode(general.FindProcessMode)
dialer.SetTcpConcurrent(general.TCPConcurrent) dialer.SetTcpConcurrent(general.TCPConcurrent)
dialer.DefaultInterface.Store(general.Interface) dialer.DefaultInterface.Store(general.Interface)
adapter.UnifiedDelay.Store(general.UnifiedDelay)
listener.ReCreateHTTP(general.Port, tunnel.Tunnel) listener.ReCreateHTTP(general.Port, tunnel.Tunnel)
listener.ReCreateSocks(general.SocksPort, tunnel.Tunnel) listener.ReCreateSocks(general.SocksPort, tunnel.Tunnel)
listener.ReCreateRedir(general.RedirPort, tunnel.Tunnel) listener.ReCreateRedir(general.RedirPort, tunnel.Tunnel)
@@ -379,54 +377,40 @@ func patchConfig(general *config.General) {
listener.ReCreateTuic(general.TuicServer, tunnel.Tunnel) listener.ReCreateTuic(general.TuicServer, tunnel.Tunnel)
tunnel.SetMode(general.Mode) tunnel.SetMode(general.Mode)
log.SetLevel(general.LogLevel) log.SetLevel(general.LogLevel)
resolver.DisableIPv6 = !general.IPv6 resolver.DisableIPv6 = !general.IPv6
} }
func patchSelectGroup() { const concurrentCount = math.MaxInt
mapping := configParams.SelectedMap
if mapping == nil { func hcCompatibleProvider(proxyProviders map[string]provider.ProxyProvider) {
return wg := sync.WaitGroup{}
} ch := make(chan struct{}, concurrentCount)
for name, proxy := range tunnel.ProxiesWithProviders() { for _, proxyProvider := range proxyProviders {
outbound, ok := proxy.(*adapter.Proxy) proxyProvider := proxyProvider
if !ok { if proxyProvider.VehicleType() == provider.Compatible {
continue log.Infoln("Start initial Compatible provider %s", proxyProvider.Name())
wg.Add(1)
ch <- struct{}{}
go func() {
defer func() { <-ch; wg.Done() }()
if err := proxyProvider.Initial(); err != nil {
log.Errorln("initial Compatible provider %s error: %v", proxyProvider.Name(), err)
}
}()
} }
selector, ok := outbound.ProxyAdapter.(outboundgroup.SelectAble)
if !ok {
continue
}
selected, exist := mapping[name]
if !exist {
continue
}
selector.ForceSet(selected)
} }
} }
var applyLock sync.Mutex func applyConfig(isPatch bool) {
func applyConfig() error {
applyLock.Lock()
defer applyLock.Unlock()
cfg, err := config.ParseRawConfig(currentConfig) cfg, err := config.ParseRawConfig(currentConfig)
if err != nil { if err != nil {
cfg, _ = config.ParseRawConfig(config.DefaultRawConfig()) cfg, _ = config.ParseRawConfig(config.DefaultRawConfig())
} }
if configParams.TestURL != nil { if isPatch {
constant.DefaultTestURL = *configParams.TestURL
}
if configParams.IsPatch {
patchConfig(cfg.General) patchConfig(cfg.General)
} else { } else {
closeConnections() executor.ApplyConfig(cfg, true)
runtime.GC() hcCompatibleProvider(tunnel.Providers())
hub.UltraApplyConfig(cfg, true)
patchSelectGroup()
} }
return err
} }

View File

@@ -1,7 +1,6 @@
package dart_bridge package dart_bridge
/* /*
#include <stdlib.h>
#include "stdint.h" #include "stdint.h"
#include "include/dart_api_dl.h" #include "include/dart_api_dl.h"
#include "include/dart_api_dl.c" #include "include/dart_api_dl.c"
@@ -25,16 +24,14 @@ func InitDartApi(api unsafe.Pointer) {
} }
} }
func SendToPort(port int64, msg string) bool { func SendToPort(port int64, msg string) {
var obj C.Dart_CObject var obj C.Dart_CObject
obj._type = C.Dart_CObject_kString obj._type = C.Dart_CObject_kString
msgString := C.CString(msg) msgString := C.CString(msg)
defer C.free(unsafe.Pointer(msgString))
ptr := unsafe.Pointer(&obj.value[0]) ptr := unsafe.Pointer(&obj.value[0])
*(**C.char)(ptr) = msgString *(**C.char)(ptr) = msgString
isSuccess := C.GoDart_PostCObject(C.Dart_Port_DL(port), &obj) isSuccess := C.GoDart_PostCObject(C.Dart_Port_DL(port), &obj)
if !isSuccess { if !isSuccess {
return false fmt.Println("ERROR: post to port ", port, " failed", msg)
} }
return true
} }

View File

@@ -0,0 +1,29 @@
package dart_bridge
import "encoding/json"
var Port *int64
type MessageType string
const (
Log MessageType = "log"
Tun MessageType = "tun"
Delay MessageType = "delay"
Now MessageType = "now"
Process MessageType = "process"
)
type Message struct {
Type MessageType `json:"type"`
Data interface{} `json:"data"`
}
func (message *Message) Json() string {
data, _ := json.Marshal(message)
return string(data)
}
func SendMessage(message Message) {
SendToPort(*Port, message.Json())
}

View File

@@ -1,14 +1,14 @@
module core module core
go 1.21.0 go 1.20
replace github.com/metacubex/mihomo => ./Clash.Meta replace github.com/metacubex/mihomo => ./Clash.Meta
require ( require (
github.com/Kr328/tun2socket v0.0.0-20220414050025-d07c78d06d34 github.com/Kr328/tun2socket v0.0.0-20220414050025-d07c78d06d34
github.com/metacubex/mihomo v1.17.1 github.com/metacubex/mihomo v1.17.1
github.com/miekg/dns v1.1.61 github.com/miekg/dns v1.1.59
golang.org/x/net v0.26.0 golang.org/x/net v0.25.0
golang.org/x/sync v0.7.0 golang.org/x/sync v0.7.0
) )
@@ -16,7 +16,7 @@ require (
github.com/3andne/restls-client-go v0.1.6 // indirect 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.2.4 // indirect
github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 // indirect github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 // indirect
github.com/ajg/form v1.5.1 // indirect github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect
github.com/andybalholm/brotli v1.0.6 // indirect github.com/andybalholm/brotli v1.0.6 // indirect
github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/buger/jsonparser v1.1.1 // indirect github.com/buger/jsonparser v1.1.1 // indirect
@@ -30,9 +30,6 @@ require (
github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010 // indirect github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/gaukas/godicttls v0.0.4 // indirect github.com/gaukas/godicttls v0.0.4 // indirect
github.com/go-chi/chi/v5 v5.0.14 // indirect
github.com/go-chi/cors v1.2.1 // indirect
github.com/go-chi/render v1.0.3 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/gobwas/httphead v0.1.0 // indirect github.com/gobwas/httphead v0.1.0 // indirect
@@ -43,26 +40,24 @@ require (
github.com/google/go-cmp v0.6.0 // indirect github.com/google/go-cmp v0.6.0 // indirect
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
github.com/hashicorp/yamux v0.1.1 // indirect github.com/hashicorp/yamux v0.1.1 // indirect
github.com/insomniacslk/dhcp v0.0.0-20240529192340-51bc6136a0a6 // indirect github.com/insomniacslk/dhcp v0.0.0-20240419123447-f1cffa2c0c49 // indirect
github.com/josharian/native v1.1.0 // indirect github.com/josharian/native v1.1.0 // indirect
github.com/klauspost/compress v1.17.9 // indirect github.com/klauspost/compress v1.17.4 // indirect
github.com/klauspost/cpuid/v2 v2.2.8 // indirect github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 // indirect github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 // indirect
github.com/mailru/easyjson v0.7.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect
github.com/mdlayher/netlink v1.7.2 // indirect github.com/mdlayher/netlink v1.7.2 // indirect
github.com/mdlayher/socket v0.4.1 // indirect github.com/mdlayher/socket v0.4.1 // indirect
github.com/metacubex/chacha v0.1.0 // indirect
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 // indirect github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 // indirect
github.com/metacubex/gvisor v0.0.0-20240320004321-933faba989ec // indirect github.com/metacubex/gvisor v0.0.0-20240320004321-933faba989ec // indirect
github.com/metacubex/quic-go v0.45.1-0.20240610004319-163fee60637e // indirect github.com/metacubex/quic-go v0.43.2-0.20240518033621-2c3d14c6b38e // indirect
github.com/metacubex/randv2 v0.2.0 // indirect
github.com/metacubex/sing-quic v0.0.0-20240518034124-7696d3f7da72 // indirect github.com/metacubex/sing-quic v0.0.0-20240518034124-7696d3f7da72 // indirect
github.com/metacubex/sing-shadowsocks v0.2.7 // indirect github.com/metacubex/sing-shadowsocks v0.2.6 // indirect
github.com/metacubex/sing-shadowsocks2 v0.2.1 // indirect github.com/metacubex/sing-shadowsocks2 v0.2.0 // indirect
github.com/metacubex/sing-tun v0.2.7-0.20240719141246-19c49ac9589d // indirect github.com/metacubex/sing-tun v0.2.7-0.20240512075008-89e7c6208eec // indirect
github.com/metacubex/sing-vmess v0.1.9-0.20240719134745-1df6fb20bbf9 // indirect github.com/metacubex/sing-vmess v0.1.9-0.20231207122118-72303677451f // indirect
github.com/metacubex/sing-wireguard v0.0.0-20240618022557-a6efaa37127a // indirect github.com/metacubex/sing-wireguard v0.0.0-20240321042214-224f96122a63 // indirect
github.com/metacubex/tfo-go v0.0.0-20240228025757-be1269474a66 // indirect github.com/metacubex/tfo-go v0.0.0-20240228025757-be1269474a66 // indirect
github.com/metacubex/utls v1.6.6 // indirect github.com/metacubex/utls v1.6.6 // indirect
github.com/mroth/weightedrand/v2 v2.1.0 // indirect github.com/mroth/weightedrand/v2 v2.1.0 // indirect
@@ -72,20 +67,18 @@ require (
github.com/oschwald/maxminddb-golang v1.12.0 // indirect github.com/oschwald/maxminddb-golang v1.12.0 // indirect
github.com/pierrec/lz4/v4 v4.1.14 // indirect github.com/pierrec/lz4/v4 v4.1.14 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/puzpuzpuz/xsync/v3 v3.2.0 // indirect github.com/puzpuzpuz/xsync/v3 v3.1.0 // indirect
github.com/quic-go/qpack v0.4.0 // indirect github.com/quic-go/qpack v0.4.0 // indirect
github.com/quic-go/qtls-go1-20 v0.4.1 // indirect github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a // indirect github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a // indirect
github.com/sagernet/fswatch v0.1.1 // indirect github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 // indirect
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect github.com/sagernet/sing v0.3.8 // indirect
github.com/sagernet/nftables v0.3.0-beta.4 // indirect
github.com/sagernet/sing v0.5.0-alpha.13 // indirect
github.com/sagernet/sing-mux v0.2.1-0.20240124034317-9bfb33698bb6 // indirect github.com/sagernet/sing-mux v0.2.1-0.20240124034317-9bfb33698bb6 // indirect
github.com/sagernet/sing-shadowtls v0.1.4 // indirect github.com/sagernet/sing-shadowtls v0.1.4 // indirect
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 // indirect github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 // indirect
github.com/sagernet/wireguard-go v0.0.0-20231209092712-9a439356a62e // indirect github.com/sagernet/wireguard-go v0.0.0-20231209092712-9a439356a62e // indirect
github.com/samber/lo v1.39.0 // indirect github.com/samber/lo v1.39.0 // indirect
github.com/shirou/gopsutil/v3 v3.24.5 // indirect github.com/shirou/gopsutil/v3 v3.24.4 // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b // indirect github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b // indirect
github.com/sina-ghaderi/rabaead v0.0.0-20220730151906-ab6e06b96e8c // indirect github.com/sina-ghaderi/rabaead v0.0.0-20220730151906-ab6e06b96e8c // indirect
@@ -94,21 +87,21 @@ require (
github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect github.com/tklauser/numcpus v0.6.1 // indirect
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect
github.com/vishvananda/netns v0.0.4 // indirect github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect
gitlab.com/go-extension/aes-ccm v0.0.0-20230221065045-e58665ef23c7 // indirect github.com/zhangyunhao116/fastrand v0.4.0 // indirect
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect
go.uber.org/mock v0.4.0 // indirect go.uber.org/mock v0.4.0 // indirect
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
golang.org/x/crypto v0.24.0 // indirect golang.org/x/crypto v0.23.0 // indirect
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect
golang.org/x/mod v0.18.0 // indirect golang.org/x/mod v0.17.0 // indirect
golang.org/x/sys v0.22.0 // indirect golang.org/x/sys v0.20.0 // indirect
golang.org/x/text v0.16.0 // indirect golang.org/x/text v0.15.0 // indirect
golang.org/x/time v0.5.0 // indirect golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.22.0 // indirect golang.org/x/tools v0.21.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect google.golang.org/protobuf v1.34.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
lukechampine.com/blake3 v1.3.0 // indirect lukechampine.com/blake3 v1.3.0 // indirect
) )

View File

@@ -7,8 +7,8 @@ github.com/RyuaNerin/go-krypto v1.2.4 h1:mXuNdK6M317aPV0llW6Xpjbo4moOlPF7Yxz4tb4
github.com/RyuaNerin/go-krypto v1.2.4/go.mod h1:QqCYkoutU3yInyD9INt2PGolVRsc3W4oraQadVGXJ/8= github.com/RyuaNerin/go-krypto v1.2.4/go.mod h1:QqCYkoutU3yInyD9INt2PGolVRsc3W4oraQadVGXJ/8=
github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 h1:cDVUiFo+npB0ZASqnw4q90ylaVAbnYyx0JYqK4YcGok= 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/Yawning/aez v0.0.0-20211027044916-e49e68abd344/go.mod h1:9pIqrY6SXNL8vjRQE5Hd/OL5GyK/9MrGUWs87z/eFfk=
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI= github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
@@ -35,25 +35,16 @@ github.com/ericlagergren/aegis v0.0.0-20230312195928-b4ce538b56f9/go.mod h1:hkIF
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 h1:8j2RH289RJplhA6WfdaPqzg1MjH2K8wX5e0uhAxrw2g= 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/polyval v0.0.0-20220411101811-e25bc10ba391/go.mod h1:K2R7GhgxrlJzHw2qiPWsCZXf/kXEJN9PLnQK73Ll0po=
github.com/ericlagergren/saferand v0.0.0-20220206064634-960a4dd2bc5c h1:RUzBDdZ+e/HEe2Nh8lYsduiPAZygUfVXJn0Ncj5sHMg= github.com/ericlagergren/saferand v0.0.0-20220206064634-960a4dd2bc5c h1:RUzBDdZ+e/HEe2Nh8lYsduiPAZygUfVXJn0Ncj5sHMg=
github.com/ericlagergren/saferand v0.0.0-20220206064634-960a4dd2bc5c/go.mod h1:ETASDWf/FmEb6Ysrtd1QhjNedUU/ZQxBCRLh60bQ/UI=
github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1 h1:tlDMEdcPRQKBEz5nGDMvswiajqh7k8ogWRlhRwKy5mY= github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1 h1:tlDMEdcPRQKBEz5nGDMvswiajqh7k8ogWRlhRwKy5mY=
github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1/go.mod h1:4RfsapbGx2j/vU5xC/5/9qB3kn9Awp1YDiEnN43QrJ4= 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 h1:fuGucgPk5dN6wzfnxl3D0D3rVLw4v2SbBT9jb4VnxzA=
github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010/go.mod h1:JtBcj7sBuTTRupn7c2bFspMDIObMJsVK8TeUvpShPok= github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010/go.mod h1:JtBcj7sBuTTRupn7c2bFspMDIObMJsVK8TeUvpShPok=
github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA= github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA=
github.com/frankban/quicktest v1.14.5/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= 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.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk= github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk=
github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI= github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI=
github.com/go-chi/chi/v5 v5.0.14 h1:PyEwo2Vudraa0x/Wl6eDRRW2NXBvekgfxyydcM0WGE0=
github.com/go-chi/chi/v5 v5.0.14/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
@@ -69,33 +60,30 @@ github.com/gofrs/uuid/v5 v5.2.0 h1:qw1GMx6/y8vhVsx626ImfKMuS5CvJmhIKKtuyvfajMM=
github.com/gofrs/uuid/v5 v5.2.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= github.com/gofrs/uuid/v5 v5.2.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/tink/go v1.6.1 h1:t7JHqO8Ath2w2ig5vjwQYJzhGEZymedQc90lQXUBa4I= github.com/google/tink/go v1.6.1 h1:t7JHqO8Ath2w2ig5vjwQYJzhGEZymedQc90lQXUBa4I=
github.com/google/tink/go v1.6.1/go.mod h1:IGW53kTgag+st5yPhKKwJ6u2l+SSp5/v9XF7spovjlY=
github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/insomniacslk/dhcp v0.0.0-20240529192340-51bc6136a0a6 h1:dh8D8FksyMhD64mRMbUhZHWYJfNoNMCxfVq6eexleMw= github.com/insomniacslk/dhcp v0.0.0-20240419123447-f1cffa2c0c49 h1:/OuvSMGT9+xnyZ+7MZQ1zdngaCCAdPoSw8B/uurZ7pg=
github.com/insomniacslk/dhcp v0.0.0-20240529192340-51bc6136a0a6/go.mod h1:KclMyHxX06VrVr0DJmeFSUb1ankt7xTfoOA35pCkoic= github.com/insomniacslk/dhcp v0.0.0-20240419123447-f1cffa2c0c49/go.mod h1:KclMyHxX06VrVr0DJmeFSUb1ankt7xTfoOA35pCkoic=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 h1:EnfXoSqDfSNJv0VBNqY/88RNnhSGYkrHaO0mmFGbVsc= github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 h1:EnfXoSqDfSNJv0VBNqY/88RNnhSGYkrHaO0mmFGbVsc=
@@ -106,34 +94,30 @@ github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
github.com/metacubex/chacha v0.1.0 h1:tg9RSJ18NvL38cCWNyYH1eiG6qDCyyXIaTLQthon0sc=
github.com/metacubex/chacha v0.1.0/go.mod h1:Djn9bPZxLTXbJFSeyo0/qzEzQI+gUSSzttuzZM75GH8=
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvOzK9ubNCCkQ+ldc4YSH/rILn53l/xGBFHHI= github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvOzK9ubNCCkQ+ldc4YSH/rILn53l/xGBFHHI=
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88= github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88=
github.com/metacubex/gvisor v0.0.0-20240320004321-933faba989ec h1:HxreOiFTUrJXJautEo8rnE1uKTVGY8wtZepY1Tii/Nc= github.com/metacubex/gvisor v0.0.0-20240320004321-933faba989ec h1:HxreOiFTUrJXJautEo8rnE1uKTVGY8wtZepY1Tii/Nc=
github.com/metacubex/gvisor v0.0.0-20240320004321-933faba989ec/go.mod h1:8BVmQ+3cxjqzWElafm24rb2Ae4jRI6vAXNXWqWjfrXw= github.com/metacubex/gvisor v0.0.0-20240320004321-933faba989ec/go.mod h1:8BVmQ+3cxjqzWElafm24rb2Ae4jRI6vAXNXWqWjfrXw=
github.com/metacubex/quic-go v0.45.1-0.20240610004319-163fee60637e h1:bLYn3GuRvWDcBDAkIv5kUYIhzHwafDVq635BuybnKqI= github.com/metacubex/quic-go v0.43.2-0.20240518033621-2c3d14c6b38e h1:Nzwe08FNIJpExWpy9iXkG336dN/8nJqn69yijB7vJ8g=
github.com/metacubex/quic-go v0.45.1-0.20240610004319-163fee60637e/go.mod h1:Yza2H7Ax1rxWPUcJx0vW+oAt9EsPuSiyQFhFabUPzwU= github.com/metacubex/quic-go v0.43.2-0.20240518033621-2c3d14c6b38e/go.mod h1:uXHODgJFUfUnkkCMWLd5Er6L5QY/LFRZb9LD5jyyhsk=
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-20240518034124-7696d3f7da72 h1:Wr4g1HCb5Z/QIFwFiVNjO2qL+dRu25+Mdn9xtAZZ+ew= github.com/metacubex/sing-quic v0.0.0-20240518034124-7696d3f7da72 h1:Wr4g1HCb5Z/QIFwFiVNjO2qL+dRu25+Mdn9xtAZZ+ew=
github.com/metacubex/sing-quic v0.0.0-20240518034124-7696d3f7da72/go.mod h1:g7Mxj7b7zm7YVqD975mk/hSmrb0A0G4bVvIMr2MMzn8= github.com/metacubex/sing-quic v0.0.0-20240518034124-7696d3f7da72/go.mod h1:g7Mxj7b7zm7YVqD975mk/hSmrb0A0G4bVvIMr2MMzn8=
github.com/metacubex/sing-shadowsocks v0.2.7 h1:9f3Dt2+71TNp0e202llA2ug5h/rkWs2EZxQ5IMpf+9g= github.com/metacubex/sing-shadowsocks v0.2.6 h1:6oEB3QcsFYnNiFeoevcXrCwJ3sAablwVSgtE9R3QeFQ=
github.com/metacubex/sing-shadowsocks v0.2.7/go.mod h1:X3x88XtJpBxG0W0/ECOJL6Ib0SJ3xdniAkU/6/RMWU0= github.com/metacubex/sing-shadowsocks v0.2.6/go.mod h1:zIkMeSnb8Mbf4hdqhw0pjzkn1d99YJ3JQm/VBg5WMTg=
github.com/metacubex/sing-shadowsocks2 v0.2.1 h1:XIZBXlazp8EEoPp1S0DViAhLkJakjQ2f+AOwwdKKNYg= github.com/metacubex/sing-shadowsocks2 v0.2.0 h1:hqwT/AfI5d5UdPefIzR6onGHJfDXs5zgOM5QSgaM/9A=
github.com/metacubex/sing-shadowsocks2 v0.2.1/go.mod h1:BhOug03a/RbI7y6hp6q+6ITM1dXjnLTmeWBHSTwvv2Q= github.com/metacubex/sing-shadowsocks2 v0.2.0/go.mod h1:LCKF6j1P94zN8ZS+LXRK1gmYTVGB3squivBSXAFnOg8=
github.com/metacubex/sing-tun v0.2.7-0.20240719141246-19c49ac9589d h1:iYlepjRCYlPXtELupDL+pQjGqkCnQz4KQOfKImP9sog= github.com/metacubex/sing-tun v0.2.7-0.20240512075008-89e7c6208eec h1:K4Wq3GOdLZ/xcqwyzAt4kmYQrjokyKQ3u/Xh5Yft14U=
github.com/metacubex/sing-tun v0.2.7-0.20240719141246-19c49ac9589d/go.mod h1:olbEx9yVcaw5tHTNlRamRoxmMKcvDvcVS1YLnQGzvWE= github.com/metacubex/sing-tun v0.2.7-0.20240512075008-89e7c6208eec/go.mod h1:4VsMwZH1IlgPGFK1ZbBomZ/B2MYkTgs2+gnBAr5GOIo=
github.com/metacubex/sing-vmess v0.1.9-0.20240719134745-1df6fb20bbf9 h1:OAXiCosqY8xKDp3pqTW3qbrCprZ1l6WkrXSFSCwyY4I= github.com/metacubex/sing-vmess v0.1.9-0.20231207122118-72303677451f h1:QjXrHKbTMBip/C+R79bvbfr42xH1gZl3uFb0RELdZiQ=
github.com/metacubex/sing-vmess v0.1.9-0.20240719134745-1df6fb20bbf9/go.mod h1:olVkD4FChQ5gKMHG4ZzuD7+fMkJY1G8vwOKpRehjrmY= github.com/metacubex/sing-vmess v0.1.9-0.20231207122118-72303677451f/go.mod h1:olVkD4FChQ5gKMHG4ZzuD7+fMkJY1G8vwOKpRehjrmY=
github.com/metacubex/sing-wireguard v0.0.0-20240618022557-a6efaa37127a h1:NpSGclHJUYndUwBmyIpFBSoBVg8PoVX7QQKhYg0DjM0= github.com/metacubex/sing-wireguard v0.0.0-20240321042214-224f96122a63 h1:AGyIB55UfQm/0ZH0HtQO9u3l//yjtHUpjeRjjPGfGRI=
github.com/metacubex/sing-wireguard v0.0.0-20240618022557-a6efaa37127a/go.mod h1:uY+BYb0UEknLrqvbGcwi9i++KgrKxsurysgI6G1Pveo= github.com/metacubex/sing-wireguard v0.0.0-20240321042214-224f96122a63/go.mod h1:uY+BYb0UEknLrqvbGcwi9i++KgrKxsurysgI6G1Pveo=
github.com/metacubex/tfo-go v0.0.0-20240228025757-be1269474a66 h1:as/aO/fM8nv4W4pOr9EETP6kV/Oaujk3fUNyQSJK61c= github.com/metacubex/tfo-go v0.0.0-20240228025757-be1269474a66 h1:as/aO/fM8nv4W4pOr9EETP6kV/Oaujk3fUNyQSJK61c=
github.com/metacubex/tfo-go v0.0.0-20240228025757-be1269474a66/go.mod h1:c7bVFM9f5+VzeZ/6Kg77T/jrg1Xp8QpqlSHvG/aXVts= github.com/metacubex/tfo-go v0.0.0-20240228025757-be1269474a66/go.mod h1:c7bVFM9f5+VzeZ/6Kg77T/jrg1Xp8QpqlSHvG/aXVts=
github.com/metacubex/utls v1.6.6 h1:3D12YKHTf2Z41UPhQU2dWerNWJ5TVQD9gKoQ+H+iLC8= github.com/metacubex/utls v1.6.6 h1:3D12YKHTf2Z41UPhQU2dWerNWJ5TVQD9gKoQ+H+iLC8=
github.com/metacubex/utls v1.6.6/go.mod h1:+WLFUnXjcpdxXCnyX25nggw8C6YonZ8zOK2Zm/oRvdo= github.com/metacubex/utls v1.6.6/go.mod h1:+WLFUnXjcpdxXCnyX25nggw8C6YonZ8zOK2Zm/oRvdo=
github.com/miekg/dns v1.1.61 h1:nLxbwF3XxhwVSm8g9Dghm9MHPaUZuqhPiGL+675ZmEs= github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs=
github.com/miekg/dns v1.1.61/go.mod h1:mnAarhS3nWaW+NVP2wTkYVIZyHNJ098SJZUki3eykwQ= github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk=
github.com/mroth/weightedrand/v2 v2.1.0 h1:o1ascnB1CIVzsqlfArQQjeMy1U0NcIbBO5rfd5E/OeU= github.com/mroth/weightedrand/v2 v2.1.0 h1:o1ascnB1CIVzsqlfArQQjeMy1U0NcIbBO5rfd5E/OeU=
github.com/mroth/weightedrand/v2 v2.1.0/go.mod h1:f2faGsfOGOwc1p94wzHKKZyTpcJUW7OJ/9U4yfiNAOU= github.com/mroth/weightedrand/v2 v2.1.0/go.mod h1:f2faGsfOGOwc1p94wzHKKZyTpcJUW7OJ/9U4yfiNAOU=
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 h1:1102pQc2SEPp5+xrS26wEaeb26sZy6k9/ZXlZN+eXE4= github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 h1:1102pQc2SEPp5+xrS26wEaeb26sZy6k9/ZXlZN+eXE4=
@@ -141,7 +125,6 @@ github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7/go.mod h1:U
github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q= github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q=
github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k= github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k=
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg=
github.com/openacid/errors v0.8.1/go.mod h1:GUQEJJOJE3W9skHm8E8Y4phdl2LLEN8iD7c5gcGgdx0= github.com/openacid/errors v0.8.1/go.mod h1:GUQEJJOJE3W9skHm8E8Y4phdl2LLEN8iD7c5gcGgdx0=
github.com/openacid/low v0.1.21 h1:Tr2GNu4N/+rGRYdOsEHOE89cxUIaDViZbVmKz29uKGo= github.com/openacid/low v0.1.21 h1:Tr2GNu4N/+rGRYdOsEHOE89cxUIaDViZbVmKz29uKGo=
github.com/openacid/low v0.1.21/go.mod h1:q+MsKI6Pz2xsCkzV4BLj7NR5M4EX0sGz5AqotpZDVh0= github.com/openacid/low v0.1.21/go.mod h1:q+MsKI6Pz2xsCkzV4BLj7NR5M4EX0sGz5AqotpZDVh0=
@@ -156,25 +139,20 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/puzpuzpuz/xsync/v3 v3.2.0 h1:9AzuUeF88YC5bK8u2vEG1Fpvu4wgpM1wfPIExfaaDxQ= github.com/puzpuzpuz/xsync/v3 v3.1.0 h1:EewKT7/LNac5SLiEblJeUu8z5eERHrmRLnMQL2d7qX4=
github.com/puzpuzpuz/xsync/v3 v3.2.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= github.com/puzpuzpuz/xsync/v3 v3.1.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= 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/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 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs=
github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k= github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkkD2QgdTuzQG263YZ+2emfpeyGqW0= github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkkD2QgdTuzQG263YZ+2emfpeyGqW0=
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM= github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM=
github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs= github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE=
github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o= github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
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.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo=
github.com/sagernet/sing v0.5.0-alpha.13 h1:fpR4TFZfu/9V3LbHSAnnnwcaXGMF8ijmAAPoY2WHSKw= github.com/sagernet/sing v0.3.8 h1:gm4JKalPhydMYX2zFOTnnd4TXtM/16WFRqSjMepYQQk=
github.com/sagernet/sing v0.5.0-alpha.13/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing v0.3.8/go.mod h1:+60H3Cm91RnL9dpVGWDPHt0zTQImO9Vfqt9a4rSambI=
github.com/sagernet/sing-mux v0.2.1-0.20240124034317-9bfb33698bb6 h1:5bCAkvDDzSMITiHFjolBwpdqYsvycdTu71FsMEFXQ14= github.com/sagernet/sing-mux v0.2.1-0.20240124034317-9bfb33698bb6 h1:5bCAkvDDzSMITiHFjolBwpdqYsvycdTu71FsMEFXQ14=
github.com/sagernet/sing-mux v0.2.1-0.20240124034317-9bfb33698bb6/go.mod h1:khzr9AOPocLa+g53dBplwNDz4gdsyx/YM3swtAhlkHQ= github.com/sagernet/sing-mux v0.2.1-0.20240124034317-9bfb33698bb6/go.mod h1:khzr9AOPocLa+g53dBplwNDz4gdsyx/YM3swtAhlkHQ=
github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k= github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k=
@@ -185,8 +163,8 @@ github.com/sagernet/wireguard-go v0.0.0-20231209092712-9a439356a62e h1:iGH0RMv2F
github.com/sagernet/wireguard-go v0.0.0-20231209092712-9a439356a62e/go.mod h1:YbL4TKHRR6APYQv3U2RGfwLDpPYSyWz6oUlpISBEzBE= github.com/sagernet/wireguard-go v0.0.0-20231209092712-9a439356a62e/go.mod h1:YbL4TKHRR6APYQv3U2RGfwLDpPYSyWz6oUlpISBEzBE=
github.com/samber/lo v1.39.0 h1:4gTz1wUhNYLhFSKl6O+8peW0v2F4BCY034GRpU9WnuA= github.com/samber/lo v1.39.0 h1:4gTz1wUhNYLhFSKl6O+8peW0v2F4BCY034GRpU9WnuA=
github.com/samber/lo v1.39.0/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= github.com/samber/lo v1.39.0/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI= github.com/shirou/gopsutil/v3 v3.24.4 h1:dEHgzZXt4LMNm+oYELpzl9YCqV65Yr/6SfrvgRBtXeU=
github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk= github.com/shirou/gopsutil/v3 v3.24.4/go.mod h1:lTd2mdiOspcqLgAnr9/nGi71NkeMpWKdmhuxm9GusH8=
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
@@ -200,9 +178,15 @@ github.com/sina-ghaderi/rabbitio v0.0.0-20220730151941-9ce26f4f872e/go.mod h1:+e
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.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 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
@@ -214,14 +198,14 @@ github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923/go.mod h1:eLL9Nub3yfAho
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8= github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg=
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
gitlab.com/go-extension/aes-ccm v0.0.0-20230221065045-e58665ef23c7 h1:UNrDfkQqiEYzdMlNsVvBYOAJWZjdktqFE9tQh5BT2+4= github.com/zhangyunhao116/fastrand v0.4.0 h1:86QB6Y+GGgLZRFRDCjMmAS28QULwspK9sgL5d1Bx3H4=
gitlab.com/go-extension/aes-ccm v0.0.0-20230221065045-e58665ef23c7/go.mod h1:E+rxHvJG9H6PUdzq9NRG6csuLN3XUx98BfGOVWNYnXs= github.com/zhangyunhao116/fastrand v0.4.0/go.mod h1:vIyo6EyBhjGKpZv6qVlkPl4JVAklpMM4DSKzbAkMguA=
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec h1:FpfFs4EhNehiVfzQttTuxanPIT43FtkkCFypIod8LHo= gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec h1:FpfFs4EhNehiVfzQttTuxanPIT43FtkkCFypIod8LHo=
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec/go.mod h1:BZ1RAoRPbCxum9Grlv5aeksu2H8BiKehBYooU2LFiOQ= gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec/go.mod h1:BZ1RAoRPbCxum9Grlv5aeksu2H8BiKehBYooU2LFiOQ=
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
@@ -230,18 +214,18 @@ go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBs
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
@@ -261,24 +245,24 @@ 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.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.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.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw=
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -1,8 +1,5 @@
package main package main
/*
#include <stdlib.h>
*/
import "C" import "C"
import ( import (
bridge "core/dart-bridge" bridge "core/dart-bridge"
@@ -13,7 +10,7 @@ import (
"github.com/metacubex/mihomo/adapter/provider" "github.com/metacubex/mihomo/adapter/provider"
"github.com/metacubex/mihomo/common/structure" "github.com/metacubex/mihomo/common/structure"
"github.com/metacubex/mihomo/common/utils" "github.com/metacubex/mihomo/common/utils"
"github.com/metacubex/mihomo/component/updater" "github.com/metacubex/mihomo/component/mmdb"
"github.com/metacubex/mihomo/config" "github.com/metacubex/mihomo/config"
"github.com/metacubex/mihomo/constant" "github.com/metacubex/mihomo/constant"
cp "github.com/metacubex/mihomo/constant/provider" cp "github.com/metacubex/mihomo/constant/provider"
@@ -31,12 +28,8 @@ import (
var currentConfig = config.DefaultRawConfig() var currentConfig = config.DefaultRawConfig()
var configParams = ConfigExtendedParams{}
var isInit = false var isInit = false
var currentProfileName = ""
//export initClash //export initClash
func initClash(homeDirStr *C.char) bool { func initClash(homeDirStr *C.char) bool {
if !isInit { if !isInit {
@@ -67,29 +60,11 @@ func shutdownClash() bool {
return true return true
} }
//export forceGc
func forceGc() {
go func() {
log.Infoln("[APP] request force GC")
runtime.GC()
}()
}
//export setCurrentProfileName
func setCurrentProfileName(s *C.char) {
currentProfileName = C.GoString(s)
}
//export getCurrentProfileName
func getCurrentProfileName() *C.char {
return C.CString(currentProfileName)
}
//export validateConfig //export validateConfig
func validateConfig(s *C.char, port C.longlong) { func validateConfig(s *C.char, port C.longlong) {
i := int64(port) i := int64(port)
bytes := []byte(C.GoString(s))
go func() { go func() {
bytes := []byte(C.GoString(s))
_, err := config.UnmarshalRawConfig(bytes) _, err := config.UnmarshalRawConfig(bytes)
if err != nil { if err != nil {
bridge.SendToPort(i, err.Error()) bridge.SendToPort(i, err.Error())
@@ -102,21 +77,20 @@ func validateConfig(s *C.char, port C.longlong) {
//export updateConfig //export updateConfig
func updateConfig(s *C.char, port C.longlong) { func updateConfig(s *C.char, port C.longlong) {
i := int64(port) i := int64(port)
paramsString := C.GoString(s)
go func() { go func() {
paramsString := C.GoString(s)
var params = &GenerateConfigParams{} var params = &GenerateConfigParams{}
err := json.Unmarshal([]byte(paramsString), params) err := json.Unmarshal([]byte(paramsString), params)
if err != nil { if err != nil {
bridge.SendToPort(i, err.Error()) bridge.SendToPort(i, err.Error())
return return
} }
configParams = params.Params prof := decorationConfig(params.ProfilePath, *params.Config, *params.IsCompatible)
prof := decorationConfig(params.ProfilePath, params.Config)
currentConfig = prof currentConfig = prof
err = applyConfig() if *params.IsPatch {
if err != nil { applyConfig(true)
bridge.SendToPort(i, err.Error()) } else {
return applyConfig(false)
} }
bridge.SendToPort(i, "") bridge.SendToPort(i, "")
}() }()
@@ -165,35 +139,35 @@ func getProxies() *C.char {
} }
//export changeProxy //export changeProxy
func changeProxy(s *C.char) { func changeProxy(s *C.char) bool {
paramsString := C.GoString(s) go func() {
var params = &ChangeProxyParams{} paramsString := C.GoString(s)
err := json.Unmarshal([]byte(paramsString), params) var params = &ChangeProxyParams{}
if err != nil { err := json.Unmarshal([]byte(paramsString), params)
log.Infoln("Unmarshal ChangeProxyParams %v", err) if err != nil {
} log.Infoln("Unmarshal ChangeProxyParams %v", err)
groupName := *params.GroupName }
proxyName := *params.ProxyName proxies := tunnel.ProxiesWithProviders()
proxies := tunnel.ProxiesWithProviders() proxy := proxies[*params.GroupName]
group, ok := proxies[groupName] if proxy == nil {
if !ok { return
return }
} log.Infoln("change proxy %s", proxy.Name())
adapterProxy := group.(*adapter.Proxy) adapterProxy := proxy.(*adapter.Proxy)
selector, ok := adapterProxy.ProxyAdapter.(outboundgroup.SelectAble) selector, ok := adapterProxy.ProxyAdapter.(*outboundgroup.Selector)
if !ok { if !ok {
return return
} }
if err := selector.Set(*params.ProxyName); err != nil {
err = selector.Set(proxyName) return
if err == nil { }
log.Infoln("[Selector] %s selected %s", groupName, proxyName) }()
} return true
} }
//export getTraffic //export getTraffic
func getTraffic() *C.char { func getTraffic() *C.char {
up, down := statistic.DefaultManager.Current(state.OnlyProxy) up, down := statistic.DefaultManager.Now()
traffic := map[string]int64{ traffic := map[string]int64{
"up": up, "up": up,
"down": down, "down": down,
@@ -206,31 +180,11 @@ func getTraffic() *C.char {
return C.CString(string(data)) return C.CString(string(data))
} }
//export getTotalTraffic
func getTotalTraffic() *C.char {
up, down := statistic.DefaultManager.Total(state.OnlyProxy)
traffic := map[string]int64{
"up": up,
"down": down,
}
data, err := json.Marshal(traffic)
if err != nil {
fmt.Println("Error:", err)
return C.CString("")
}
return C.CString(string(data))
}
//export resetTraffic
func resetTraffic() {
statistic.DefaultManager.ResetStatistic()
}
//export asyncTestDelay //export asyncTestDelay
func asyncTestDelay(s *C.char, port C.longlong) { func asyncTestDelay(s *C.char, port C.longlong) {
i := int64(port) i := int64(port)
paramsString := C.GoString(s)
go func() { go func() {
paramsString := C.GoString(s)
var params = &TestDelayParams{} var params = &TestDelayParams{}
err := json.Unmarshal([]byte(paramsString), params) err := json.Unmarshal([]byte(paramsString), params)
if err != nil { if err != nil {
@@ -300,7 +254,7 @@ func getConnections() *C.char {
} }
//export closeConnections //export closeConnections
func closeConnections() { func closeConnections() bool {
statistic.DefaultManager.Range(func(c statistic.Tracker) bool { statistic.DefaultManager.Range(func(c statistic.Tracker) bool {
err := c.Close() err := c.Close()
if err != nil { if err != nil {
@@ -308,28 +262,27 @@ func closeConnections() {
} }
return true return true
}) })
return true
} }
//export closeConnection //export closeConnection
func closeConnection(id *C.char) { func closeConnection(id *C.char) bool {
connectionId := C.GoString(id) connectionId := C.GoString(id)
c := statistic.DefaultManager.Get(connectionId)
if c == nil { err := statistic.DefaultManager.Get(connectionId).Close()
return if err != nil {
return false
} }
_ = c.Close() return true
} }
//export getProviders //export getProviders
func getProviders() *C.char { func getProviders() *C.char {
data, err := json.Marshal(tunnel.Providers()) data, err := json.Marshal(tunnel.Providers())
var msg *C.char
if err != nil { if err != nil {
msg = C.CString("") return C.CString("")
return msg
} }
msg = C.CString(string(data)) return C.CString(string(data))
return msg
} }
//export getProvider //export getProvider
@@ -379,9 +332,10 @@ func getExternalProviders() *C.char {
//export updateExternalProvider //export updateExternalProvider
func updateExternalProvider(providerName *C.char, providerType *C.char, port C.longlong) { func updateExternalProvider(providerName *C.char, providerType *C.char, port C.longlong) {
i := int64(port) i := int64(port)
providerNameString := C.GoString(providerName)
providerTypeString := C.GoString(providerType)
go func() { go func() {
providerNameString := C.GoString(providerName)
providerTypeString := C.GoString(providerType)
switch providerTypeString { switch providerTypeString {
case "Proxy": case "Proxy":
providers := tunnel.Providers() providers := tunnel.Providers()
@@ -397,26 +351,20 @@ func updateExternalProvider(providerName *C.char, providerType *C.char, port C.l
bridge.SendToPort(i, err.Error()) bridge.SendToPort(i, err.Error())
return return
} }
case "MMDB":
err := updater.UpdateMMDB(constant.Path.Resolve(providerNameString))
if err != nil {
bridge.SendToPort(i, err.Error())
return
}
case "ASN":
err := updater.UpdateASN(constant.Path.Resolve(providerNameString))
if err != nil {
bridge.SendToPort(i, err.Error())
return
}
case "GeoIp": case "GeoIp":
err := updater.UpdateGeoIp(constant.Path.Resolve(providerNameString)) err := mmdb.DownloadMMDB(constant.Path.Resolve(providerNameString))
if err != nil { if err != nil {
bridge.SendToPort(i, err.Error()) bridge.SendToPort(i, err.Error())
return return
} }
case "GeoSite": case "GeoSite":
err := updater.UpdateGeoSite(constant.Path.Resolve(providerNameString)) err := mmdb.DownloadGeoSite(constant.Path.Resolve(providerNameString))
if err != nil {
bridge.SendToPort(i, err.Error())
return
}
case "ASN":
err := mmdb.DownloadASN(constant.Path.Resolve(providerNameString))
if err != nil { if err != nil {
bridge.SendToPort(i, err.Error()) bridge.SendToPort(i, err.Error())
return return
@@ -427,19 +375,10 @@ func updateExternalProvider(providerName *C.char, providerType *C.char, port C.l
} }
//export initNativeApiBridge //export initNativeApiBridge
func initNativeApiBridge(api unsafe.Pointer) { func initNativeApiBridge(api unsafe.Pointer, port C.longlong) {
bridge.InitDartApi(api) bridge.InitDartApi(api)
}
//export initMessage
func initMessage(port C.longlong) {
i := int64(port) i := int64(port)
Port = i bridge.Port = &i
}
//export freeCString
func freeCString(s *C.char) {
C.free(unsafe.Pointer(s))
} }
func init() { func init() {
@@ -452,21 +391,9 @@ func init() {
} else { } else {
delayData.Value = int32(delay) delayData.Value = int32(delay)
} }
SendMessage(Message{ bridge.SendMessage(bridge.Message{
Type: DelayMessage, Type: bridge.Delay,
Data: delayData, Data: delayData,
}) })
} }
statistic.DefaultRequestNotify = func(c statistic.Tracker) {
SendMessage(Message{
Type: RequestMessage,
Data: c,
})
}
executor.DefaultProxyProviderLoadedHook = func(providerName string) {
SendMessage(Message{
Type: LoadedMessage,
Data: providerName,
})
}
} }

View File

@@ -2,6 +2,7 @@ package main
import "C" import "C"
import ( import (
bridge "core/dart-bridge"
"github.com/metacubex/mihomo/common/observable" "github.com/metacubex/mihomo/common/observable"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
) )
@@ -17,14 +18,11 @@ func startLog() {
logSubscriber = log.Subscribe() logSubscriber = log.Subscribe()
go func() { go func() {
for logData := range logSubscriber { for logData := range logSubscriber {
if logData.LogLevel < log.Level() { message := &bridge.Message{
continue Type: bridge.Log,
}
message := &Message{
Type: LogMessage,
Data: logData, Data: logData,
} }
SendMessage(*message) bridge.SendMessage(*message)
} }
}() }()
} }

View File

@@ -1,77 +0,0 @@
package main
import (
bridge "core/dart-bridge"
"encoding/json"
"github.com/metacubex/mihomo/constant"
)
var Port int64
var ServicePort int64
type MessageType string
const (
LogMessage MessageType = "log"
ProtectMessage MessageType = "protect"
DelayMessage MessageType = "delay"
ProcessMessage MessageType = "process"
RequestMessage MessageType = "request"
StartedMessage MessageType = "started"
LoadedMessage MessageType = "loaded"
)
type Delay struct {
Name string `json:"name"`
Value int32 `json:"value"`
}
type Process struct {
Id int64 `json:"id"`
Metadata *constant.Metadata `json:"metadata"`
}
type Message struct {
Type MessageType `json:"type"`
Data interface{} `json:"data"`
}
func (message *Message) Json() (string, error) {
data, err := json.Marshal(message)
return string(data), err
}
func SendMessage(message Message) {
s, err := message.Json()
if err != nil {
return
}
if handler, ok := messageHandlers[message.Type]; ok {
handler(s)
} else {
sendToPort(s)
}
}
var messageHandlers = map[MessageType]func(string) bool{
ProtectMessage: sendToServicePort,
ProcessMessage: sendToServicePort,
StartedMessage: conditionalSend,
LoadedMessage: conditionalSend,
}
func sendToPort(s string) bool {
return bridge.SendToPort(Port, s)
}
func sendToServicePort(s string) bool {
return bridge.SendToPort(ServicePort, s)
}
func conditionalSend(s string) bool {
isSuccess := sendToPort(s)
if !isSuccess {
return sendToServicePort(s)
}
return isSuccess
}

View File

@@ -1,42 +0,0 @@
//go:build android
package platform
import "syscall"
var nullFd int
var maxFdCount int
func init() {
fd, err := syscall.Open("/dev/null", syscall.O_WRONLY, 0644)
if err != nil {
panic(err.Error())
}
nullFd = fd
var limit syscall.Rlimit
if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil {
maxFdCount = 1024
} else {
maxFdCount = int(limit.Cur)
}
maxFdCount = maxFdCount / 4 * 3
}
func ShouldBlockConnection() bool {
fd, err := syscall.Dup(nullFd)
if err != nil {
return true
}
_ = syscall.Close(fd)
if fd > maxFdCount {
return true
}
return false
}

View File

@@ -1,81 +1,3 @@
//go:build android //go:build android
package main package main
import "C"
import (
"encoding/json"
"errors"
"github.com/metacubex/mihomo/component/process"
"github.com/metacubex/mihomo/constant"
"sync"
"sync/atomic"
"time"
)
type ProcessMap struct {
m sync.Map
}
func (cm *ProcessMap) Store(key int64, value string) {
cm.m.Store(key, value)
}
func (cm *ProcessMap) Load(key int64) (string, bool) {
value, ok := cm.m.Load(key)
if !ok || value == nil {
return "", false
}
return value.(string), true
}
var counter int64 = 0
var processMap ProcessMap
func init() {
process.DefaultPackageNameResolver = func(metadata *constant.Metadata) (string, error) {
if metadata == nil {
return "", process.ErrInvalidNetwork
}
id := atomic.AddInt64(&counter, 1)
timeout := time.After(200 * time.Millisecond)
SendMessage(Message{
Type: ProcessMessage,
Data: Process{
Id: id,
Metadata: metadata,
},
})
for {
select {
case <-timeout:
return "", errors.New("package resolver timeout")
default:
value, exists := processMap.Load(id)
if exists {
return value, nil
}
time.Sleep(20 * time.Millisecond)
}
}
}
}
//export setProcessMap
func setProcessMap(s *C.char) {
if s == nil {
return
}
paramsString := C.GoString(s)
go func() {
var processMapItem = &ProcessMapItem{}
err := json.Unmarshal([]byte(paramsString), processMapItem)
if err == nil {
processMap.Store(processMapItem.Id, processMapItem.Value)
}
}()
}

View File

@@ -1,47 +0,0 @@
package main
import "C"
import (
"encoding/json"
"fmt"
)
type AccessControl struct {
Mode string `json:"mode"`
AcceptList []string `json:"acceptList"`
RejectList []string `json:"rejectList"`
IsFilterSystemApp bool `json:"isFilterSystemApp"`
}
type AndroidProps struct {
AccessControl *AccessControl `json:"accessControl"`
AllowBypass bool `json:"allowBypass"`
SystemProxy bool `json:"systemProxy"`
}
type State struct {
AndroidProps
MixedPort int `json:"mixedPort"`
OnlyProxy bool `json:"onlyProxy"`
}
var state State
//export getState
func getState() *C.char {
data, err := json.Marshal(state)
if err != nil {
fmt.Println("Error:", err)
return C.CString("")
}
return C.CString(string(data))
}
//export setState
func setState(s *C.char) {
paramsString := C.GoString(s)
err := json.Unmarshal([]byte(paramsString), &state)
if err != nil {
return
}
}

View File

@@ -4,42 +4,20 @@ package main
import "C" import "C"
import ( import (
"core/platform"
t "core/tun" t "core/tun"
"errors"
"github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
"golang.org/x/sync/semaphore" "golang.org/x/sync/semaphore"
"strconv"
"sync" "sync"
"sync/atomic"
"syscall" "syscall"
"time" "time"
) )
var tunLock sync.Mutex var tunLock sync.Mutex
var tun *t.Tun var tun *t.Tun
var runTime *time.Time
type FdMap struct {
m sync.Map
}
func (cm *FdMap) Store(key int64) {
cm.m.Store(key, struct{}{})
}
func (cm *FdMap) Load(key int64) bool {
_, ok := cm.m.Load(key)
return ok
}
var fdMap FdMap
//export startTUN //export startTUN
func startTUN(fd C.int, port C.longlong) { func startTUN(fd C.int) {
i := int64(port)
ServicePort = i
go func() { go func() {
tunLock.Lock() tunLock.Lock()
defer tunLock.Unlock() defer tunLock.Unlock()
@@ -48,7 +26,6 @@ func startTUN(fd C.int, port C.longlong) {
tun.Close() tun.Close()
tun = nil tun = nil
} }
f := int(fd) f := int(fd)
gateway := "172.16.0.1/30" gateway := "172.16.0.1/30"
portal := "172.16.0.2" portal := "172.16.0.2"
@@ -58,6 +35,8 @@ func startTUN(fd C.int, port C.longlong) {
closer, err := t.Start(f, gateway, portal, dns) closer, err := t.Start(f, gateway, portal, dns)
applyConfig(true)
if err != nil { if err != nil {
log.Errorln("startTUN error: %v", err) log.Errorln("startTUN error: %v", err)
tempTun.Close() tempTun.Close()
@@ -66,24 +45,17 @@ func startTUN(fd C.int, port C.longlong) {
tempTun.Closer = closer tempTun.Closer = closer
tun = tempTun tun = tempTun
now := time.Now()
runTime = &now
SendMessage(Message{
Type: StartedMessage,
Data: strconv.FormatInt(runTime.UnixMilli(), 10),
})
}() }()
} }
//export getRunTime //export updateMarkSocketPort
func getRunTime() *C.char { func updateMarkSocketPort(markSocketPort C.longlong) bool {
if runTime == nil { tunLock.Lock()
return C.CString("") defer tunLock.Unlock()
} //if tun != nil {
return C.CString(strconv.FormatInt(runTime.UnixMilli(), 10)) // tun.MarkSocketPort = int64(markSocketPort)
//}
return true
} }
//export stopTun //export stopTun
@@ -92,69 +64,20 @@ func stopTun() {
tunLock.Lock() tunLock.Lock()
defer tunLock.Unlock() defer tunLock.Unlock()
runTime = nil
if tun != nil { if tun != nil {
tun.Close() tun.Close()
applyConfig(true)
tun = nil tun = nil
} }
}() }()
} }
var errBlocked = errors.New("blocked")
//export setFdMap
func setFdMap(fd C.long) {
fdInt := int64(fd)
go func() {
fdMap.Store(fdInt)
}()
}
type Fd struct {
Id int64 `json:"id"`
Value int64 `json:"value"`
}
func markSocket(fd Fd) {
SendMessage(Message{
Type: ProtectMessage,
Data: fd,
})
}
var fdCounter int64 = 0
func init() { func init() {
dialer.DefaultSocketHook = func(network, address string, conn syscall.RawConn) error { dialer.DefaultSocketHook = func(network, address string, conn syscall.RawConn) error {
if platform.ShouldBlockConnection() {
return errBlocked
}
return conn.Control(func(fd uintptr) { return conn.Control(func(fd uintptr) {
if tun == nil { if tun != nil {
return tun.MarkSocket(int(fd))
} time.Sleep(time.Millisecond * 100)
fdInt := int64(fd)
timeout := time.After(100 * time.Millisecond)
id := atomic.AddInt64(&fdCounter, 1)
markSocket(Fd{
Id: id,
Value: fdInt,
})
for {
select {
case <-timeout:
return
default:
exists := fdMap.Load(id)
if exists {
return
}
time.Sleep(10 * time.Millisecond)
}
} }
}) })
} }

View File

@@ -5,6 +5,7 @@ package tun
import "C" import "C"
import ( import (
"context" "context"
bridge "core/dart-bridge"
"encoding/binary" "encoding/binary"
"github.com/Kr328/tun2socket" "github.com/Kr328/tun2socket"
"github.com/Kr328/tun2socket/nat" "github.com/Kr328/tun2socket/nat"
@@ -18,6 +19,7 @@ import (
"io" "io"
"net" "net"
"os" "os"
"strconv"
"time" "time"
) )
@@ -184,3 +186,19 @@ func Start(fd int, gateway, portal, dns string) (io.Closer, error) {
return stack, nil return stack, nil
} }
func (t *Tun) MarkSocket(fd int) {
_ = t.Limit.Acquire(context.Background(), 1)
defer t.Limit.Release(1)
if t.Closed {
return
}
message := &bridge.Message{
Type: bridge.Tun,
Data: strconv.Itoa(fd),
}
bridge.SendMessage(*message)
}

View File

@@ -82,11 +82,7 @@ class ApplicationState extends State<Application> {
super.initState(); super.initState();
globalState.appController = AppController(context); globalState.appController = AppController(context);
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
final currentContext = globalState.navigatorKey.currentContext; globalState.appController.afterInit();
if (currentContext != null) {
globalState.appController = AppController(currentContext);
}
await globalState.appController.init();
globalState.appController.initLink(); globalState.appController.initLink();
_updateGroups(); _updateGroups();
}); });
@@ -115,6 +111,7 @@ class ApplicationState extends State<Application> {
lightColorScheme: lightDynamic, lightColorScheme: lightDynamic,
darkColorScheme: darkDynamic, darkColorScheme: darkDynamic,
); );
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
globalState.appController.updateSystemColorSchemes(systemColorSchemes); globalState.appController.updateSystemColorSchemes(systemColorSchemes);
}); });
@@ -128,7 +125,8 @@ class ApplicationState extends State<Application> {
globalState.groupsUpdateTimer ??= Timer.periodic( globalState.groupsUpdateTimer ??= Timer.periodic(
httpTimeoutDuration, httpTimeoutDuration,
(timer) async { (timer) async {
await globalState.appController.updateGroupDebounce(); await globalState.appController.updateGroups();
globalState.appController.appState.sortNum++;
}, },
); );
} }
@@ -136,58 +134,58 @@ class ApplicationState extends State<Application> {
@override @override
Widget build(context) { Widget build(context) {
return AppStateContainer( return AppStateContainer(
child: ClashContainer( child: ClashMessageContainer(
child: Selector2<AppState, Config, ApplicationSelectorState>( child: _buildApp(
selector: (_, appState, config) => ApplicationSelectorState( Selector2<AppState, Config, ApplicationSelectorState>(
locale: config.locale, selector: (_, appState, config) => ApplicationSelectorState(
themeMode: config.themeMode, locale: config.locale,
primaryColor: config.primaryColor, themeMode: config.themeMode,
primaryColor: config.primaryColor,
),
builder: (_, state, child) {
return DynamicColorBuilder(
builder: (lightDynamic, darkDynamic) {
_updateSystemColorSchemes(lightDynamic, darkDynamic);
return MaterialApp(
navigatorKey: globalState.navigatorKey,
localizationsDelegates: const [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
GlobalWidgetsLocalizations.delegate
],
title: appName,
locale: other.getLocaleForString(state.locale),
supportedLocales:
AppLocalizations.delegate.supportedLocales,
themeMode: state.themeMode,
theme: ThemeData(
useMaterial3: true,
fontFamily: '',
pageTransitionsTheme: _pageTransitionsTheme,
colorScheme: _getAppColorScheme(
brightness: Brightness.light,
systemColorSchemes: systemColorSchemes,
primaryColor: state.primaryColor,
),
),
darkTheme: ThemeData(
useMaterial3: true,
fontFamily: '',
pageTransitionsTheme: _pageTransitionsTheme,
colorScheme: _getAppColorScheme(
brightness: Brightness.dark,
systemColorSchemes: systemColorSchemes,
primaryColor: state.primaryColor,
),
),
home: child,
);
},
);
},
child: const HomePage(),
), ),
builder: (_, state, child) {
return DynamicColorBuilder(
builder: (lightDynamic, darkDynamic) {
_updateSystemColorSchemes(lightDynamic, darkDynamic);
return MaterialApp(
debugShowCheckedModeBanner: false,
navigatorKey: globalState.navigatorKey,
localizationsDelegates: const [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
GlobalWidgetsLocalizations.delegate
],
builder: (_, child) {
return _buildApp(child!);
},
scrollBehavior: BaseScrollBehavior(),
title: appName,
locale: other.getLocaleForString(state.locale),
supportedLocales: AppLocalizations.delegate.supportedLocales,
themeMode: state.themeMode,
theme: ThemeData(
useMaterial3: true,
pageTransitionsTheme: _pageTransitionsTheme,
colorScheme: _getAppColorScheme(
brightness: Brightness.light,
systemColorSchemes: systemColorSchemes,
primaryColor: state.primaryColor,
),
),
darkTheme: ThemeData(
useMaterial3: true,
pageTransitionsTheme: _pageTransitionsTheme,
colorScheme: _getAppColorScheme(
brightness: Brightness.dark,
systemColorSchemes: systemColorSchemes,
primaryColor: state.primaryColor,
),
),
home: child,
);
},
);
},
child: const HomePage(),
), ),
), ),
); );

View File

@@ -35,6 +35,7 @@ class ClashCore {
clashFFI = ClashFFI(lib); clashFFI = ClashFFI(lib);
clashFFI.initNativeApiBridge( clashFFI.initNativeApiBridge(
NativeApi.initializeApiDLData, NativeApi.initializeApiDLData,
receiver.sendPort.nativePort,
); );
} }
@@ -44,10 +45,10 @@ class ClashCore {
} }
bool init(String homeDir) { bool init(String homeDir) {
final homeDirChar = homeDir.toNativeUtf8().cast<Char>(); return clashFFI.initClash(
final isInit = clashFFI.initClash(homeDirChar) == 1; homeDir.toNativeUtf8().cast(),
malloc.free(homeDirChar); ) ==
return isInit; 1;
} }
shutdown() { shutdown() {
@@ -66,12 +67,10 @@ class ClashCore {
receiver.close(); receiver.close();
} }
}); });
final dataChar = data.toNativeUtf8().cast<Char>();
clashFFI.validateConfig( clashFFI.validateConfig(
dataChar, data.toNativeUtf8().cast(),
receiver.sendPort.nativePort, receiver.sendPort.nativePort,
); );
malloc.free(dataChar);
return completer.future; return completer.future;
} }
@@ -85,49 +84,22 @@ class ClashCore {
} }
}); });
final params = json.encode(updateConfigParams); final params = json.encode(updateConfigParams);
final paramsChar = params.toNativeUtf8().cast<Char>();
clashFFI.updateConfig( clashFFI.updateConfig(
paramsChar, params.toNativeUtf8().cast(),
receiver.sendPort.nativePort, receiver.sendPort.nativePort,
); );
malloc.free(paramsChar);
return completer.future; return completer.future;
} }
initMessage() {
clashFFI.initMessage(
receiver.sendPort.nativePort,
);
}
setProfileName(String profileName) {
final profileNameChar = profileName.toNativeUtf8().cast<Char>();
clashFFI.setCurrentProfileName(
profileNameChar,
);
malloc.free(profileNameChar);
}
getProfileName() {
final currentProfileNameRaw = clashFFI.getCurrentProfileName();
final currentProfileName =
currentProfileNameRaw.cast<Utf8>().toDartString();
clashFFI.freeCString(currentProfileNameRaw);
return currentProfileName;
}
Future<List<Group>> getProxiesGroups() { Future<List<Group>> getProxiesGroups() {
final proxiesRaw = clashFFI.getProxies(); final proxiesRaw = clashFFI.getProxies();
final proxiesRawString = proxiesRaw.cast<Utf8>().toDartString(); final proxiesRawString = proxiesRaw.cast<Utf8>().toDartString();
clashFFI.freeCString(proxiesRaw);
return Isolate.run<List<Group>>(() { return Isolate.run<List<Group>>(() {
if (proxiesRawString.isEmpty) return []; final proxies = json.decode(proxiesRawString);
final proxies = (json.decode(proxiesRawString) ?? {}) as Map;
if (proxies.isEmpty) return [];
final groupNames = [ final groupNames = [
UsedProxy.GLOBAL.name, UsedProxy.GLOBAL.name,
...(proxies[UsedProxy.GLOBAL.name]["all"] as List).where((e) { ...(proxies[UsedProxy.GLOBAL.name]["all"] as List).where((e) {
final proxy = proxies[e] ?? {}; final proxy = proxies[e];
return GroupTypeExtension.valueList.contains(proxy['type']); return GroupTypeExtension.valueList.contains(proxy['type']);
}) })
]; ];
@@ -137,15 +109,10 @@ class ClashCore {
.map( .map(
(name) => proxies[name], (name) => proxies[name],
) )
.where((proxy) => proxy != null)
.toList(); .toList();
return group; return group;
}).toList(); }).toList();
return groupsRaw return groupsRaw.map((e) => Group.fromJson(e)).toList();
.map(
(e) => Group.fromJson(e),
)
.toList();
}); });
} }
@@ -153,7 +120,6 @@ class ClashCore {
final externalProvidersRaw = clashFFI.getExternalProviders(); final externalProvidersRaw = clashFFI.getExternalProviders();
final externalProvidersRawString = final externalProvidersRawString =
externalProvidersRaw.cast<Utf8>().toDartString(); externalProvidersRaw.cast<Utf8>().toDartString();
clashFFI.freeCString(externalProvidersRaw);
return Isolate.run<List<ExternalProvider>>(() { return Isolate.run<List<ExternalProvider>>(() {
final externalProviders = final externalProviders =
(json.decode(externalProvidersRawString) as List<dynamic>) (json.decode(externalProvidersRawString) as List<dynamic>)
@@ -177,23 +143,17 @@ class ClashCore {
receiver.close(); receiver.close();
} }
}); });
final providerNameChar = providerName.toNativeUtf8().cast<Char>();
final providerTypeChar = providerType.toNativeUtf8().cast<Char>();
clashFFI.updateExternalProvider( clashFFI.updateExternalProvider(
providerNameChar, providerName.toNativeUtf8().cast(),
providerTypeChar, providerType.toNativeUtf8().cast(),
receiver.sendPort.nativePort, receiver.sendPort.nativePort,
); );
malloc.free(providerNameChar);
malloc.free(providerTypeChar);
return completer.future; return completer.future;
} }
changeProxy(ChangeProxyParams changeProxyParams) { bool changeProxy(ChangeProxyParams changeProxyParams) {
final params = json.encode(changeProxyParams); final params = json.encode(changeProxyParams);
final paramsChar = params.toNativeUtf8().cast<Char>(); return clashFFI.changeProxy(params.toNativeUtf8().cast()) == 1;
clashFFI.changeProxy(paramsChar);
malloc.free(paramsChar);
} }
Future<Delay> getDelay(String proxyName) { Future<Delay> getDelay(String proxyName) {
@@ -209,70 +169,35 @@ class ClashCore {
receiver.close(); receiver.close();
} }
}); });
final delayParamsChar =
json.encode(delayParams).toNativeUtf8().cast<Char>();
clashFFI.asyncTestDelay( clashFFI.asyncTestDelay(
delayParamsChar, json.encode(delayParams).toNativeUtf8().cast(),
receiver.sendPort.nativePort, receiver.sendPort.nativePort,
); );
malloc.free(delayParamsChar);
Future.delayed(httpTimeoutDuration + moreDuration, () { Future.delayed(httpTimeoutDuration + moreDuration, () {
receiver.close(); receiver.close();
if (!completer.isCompleted) { completer.complete(
completer.complete( Delay(name: proxyName, value: -1),
Delay(name: proxyName, value: -1), );
);
}
}); });
return completer.future; return completer.future;
} }
clearEffect(String path) { clearEffect(String path) {
final pathChar = path.toNativeUtf8().cast<Char>(); clashFFI.clearEffect(path.toNativeUtf8().cast());
clashFFI.clearEffect(pathChar);
malloc.free(pathChar);
} }
VersionInfo getVersionInfo() { VersionInfo getVersionInfo() {
final versionInfoRaw = clashFFI.getVersionInfo(); final versionInfoRaw = clashFFI.getVersionInfo();
final versionInfo = json.decode(versionInfoRaw.cast<Utf8>().toDartString()); final versionInfo = json.decode(versionInfoRaw.cast<Utf8>().toDartString());
clashFFI.freeCString(versionInfoRaw);
return VersionInfo.fromJson(versionInfo); return VersionInfo.fromJson(versionInfo);
} }
setState(CoreState state) {
final stateChar = json.encode(state).toNativeUtf8().cast<Char>();
clashFFI.setState(stateChar);
malloc.free(stateChar);
}
CoreState getState() {
final stateRaw = clashFFI.getState();
final state = json.decode(
stateRaw.cast<Utf8>().toDartString(),
);
clashFFI.freeCString(stateRaw);
return CoreState.fromJson(state);
}
Traffic getTraffic() { Traffic getTraffic() {
final trafficRaw = clashFFI.getTraffic(); final trafficRaw = clashFFI.getTraffic();
final trafficMap = json.decode(trafficRaw.cast<Utf8>().toDartString()); final trafficMap = json.decode(trafficRaw.cast<Utf8>().toDartString());
clashFFI.freeCString(trafficRaw);
return Traffic.fromMap(trafficMap); return Traffic.fromMap(trafficMap);
} }
Traffic getTotalTraffic() {
final trafficRaw = clashFFI.getTotalTraffic();
final trafficMap = json.decode(trafficRaw.cast<Utf8>().toDartString());
clashFFI.freeCString(trafficRaw);
return Traffic.fromMap(trafficMap);
}
void resetTraffic() {
clashFFI.resetTraffic();
}
void startLog() { void startLog() {
clashFFI.startLog(); clashFFI.startLog();
} }
@@ -281,56 +206,21 @@ class ClashCore {
clashFFI.stopLog(); clashFFI.stopLog();
} }
startTun(int fd, int port) { startTun(int fd) {
if (!Platform.isAndroid) return; clashFFI.startTUN(fd);
clashFFI.startTUN(fd, port);
}
requestGc() {
clashFFI.forceGc();
} }
void stopTun() { void stopTun() {
clashFFI.stopTun(); clashFFI.stopTun();
} }
void setProcessMap(ProcessMapItem processMapItem) {
final processMapItemChar =
json.encode(processMapItem).toNativeUtf8().cast<Char>();
clashFFI.setProcessMap(processMapItemChar);
malloc.free(processMapItemChar);
}
void setFdMap(int fd) {
clashFFI.setFdMap(fd);
}
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));
}
List<Connection> getConnections() { List<Connection> getConnections() {
final connectionsDataRaw = clashFFI.getConnections(); final connectionsDataRaw = clashFFI.getConnections();
final connectionsData = final connectionsData =
json.decode(connectionsDataRaw.cast<Utf8>().toDartString()) as Map; json.decode(connectionsDataRaw.cast<Utf8>().toDartString()) as Map;
clashFFI.freeCString(connectionsDataRaw);
final connectionsRaw = connectionsData['connections'] as List? ?? []; final connectionsRaw = connectionsData['connections'] as List? ?? [];
return connectionsRaw.map((e) => Connection.fromJson(e)).toList(); return connectionsRaw.map((e) => Connection.fromJson(e)).toList();
} }
closeConnection(String id) {
final idChar = id.toNativeUtf8().cast<Char>();
clashFFI.closeConnection(idChar);
malloc.free(idChar);
}
closeConnections() {
clashFFI.closeConnections();
}
} }
final clashCore = ClashCore(); final clashCore = ClashCore();

File diff suppressed because it is too large Load Diff

View File

@@ -7,6 +7,18 @@ import 'package:flutter/foundation.dart';
import 'core.dart'; import 'core.dart';
abstract mixin class ClashMessageListener {
void onLog(Log log) {}
void onTun(String fd) {}
void onDelay(Delay delay) {}
void onProcess(Metadata metadata) {}
void onNow(Now now) {}
}
class ClashMessage { class ClashMessage {
StreamSubscription? subscription; StreamSubscription? subscription;
@@ -16,23 +28,23 @@ class ClashMessage {
subscription = null; subscription = null;
} }
subscription = ClashCore.receiver.listen((message) { subscription = ClashCore.receiver.listen((message) {
final m = AppMessage.fromJson(json.decode(message)); final m = Message.fromJson(json.decode(message));
for (final AppMessageListener listener in _listeners) { for (final ClashMessageListener listener in _listeners) {
switch (m.type) { switch (m.type) {
case AppMessageType.log: case MessageType.log:
listener.onLog(Log.fromJson(m.data)); listener.onLog(Log.fromJson(m.data));
break; break;
case AppMessageType.delay: case MessageType.tun:
listener.onTun(m.data);
break;
case MessageType.delay:
listener.onDelay(Delay.fromJson(m.data)); listener.onDelay(Delay.fromJson(m.data));
break; break;
case AppMessageType.request: case MessageType.process:
listener.onRequest(Connection.fromJson(m.data)); listener.onProcess(Metadata.fromJson(m.data));
break; break;
case AppMessageType.started: case MessageType.now:
listener.onStarted(m.data); listener.onNow(Now.fromJson(m.data));
break;
case AppMessageType.loaded:
listener.onLoaded(m.data);
break; break;
} }
} }
@@ -41,18 +53,18 @@ class ClashMessage {
static final ClashMessage instance = ClashMessage._(); static final ClashMessage instance = ClashMessage._();
final ObserverList<AppMessageListener> _listeners = final ObserverList<ClashMessageListener> _listeners =
ObserverList<AppMessageListener>(); ObserverList<ClashMessageListener>();
bool get hasListeners { bool get hasListeners {
return _listeners.isNotEmpty; return _listeners.isNotEmpty;
} }
void addListener(AppMessageListener listener) { void addListener(ClashMessageListener listener) {
_listeners.add(listener); _listeners.add(listener);
} }
void removeListener(AppMessageListener listener) { void removeListener(ClashMessageListener listener) {
_listeners.remove(listener); _listeners.remove(listener);
} }
} }

View File

@@ -17,7 +17,6 @@ class ClashService {
} }
const geoFileNameList = [ const geoFileNameList = [
mmdbFileName, mmdbFileName,
geoIpFileName,
geoSiteFileName, geoSiteFileName,
asnFileName, asnFileName,
]; ];

View File

@@ -1,10 +1,14 @@
import 'dart:io'; import 'dart:io';
import 'package:fl_clash/clash/clash.dart';
import 'package:fl_clash/plugins/app.dart'; import 'package:fl_clash/plugins/app.dart';
class Android { class Android {
init() async { init() async {
app?.onExit = () {}; app?.onExit = () {
clashCore.shutdown();
exit(0);
};
} }
} }

View File

@@ -22,7 +22,4 @@ export 'string.dart';
export 'app_localizations.dart'; export 'app_localizations.dart';
export 'function.dart'; export 'function.dart';
export 'package.dart'; export 'package.dart';
export 'measure.dart'; export 'measure.dart';
export 'service.dart';
export 'iterable.dart';
export 'scroll.dart';

View File

@@ -1,33 +1,16 @@
import 'dart:io';
import 'dart:ui'; import 'dart:ui';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/clash_config.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'system.dart';
const appName = "FlClash"; const appName = "FlClash";
const coreName = "clash.meta"; const coreName = "clash.meta";
const packageName = "com.follow.clash"; const packageName = "FlClash";
const httpTimeoutDuration = Duration(milliseconds: 5000); const httpTimeoutDuration = Duration(milliseconds: 5000);
const moreDuration = Duration(milliseconds: 100); const moreDuration = Duration(milliseconds: 100);
const animateDuration = Duration(milliseconds: 100);
const defaultUpdateDuration = Duration(days: 1); const defaultUpdateDuration = Duration(days: 1);
const mmdbFileName = "geoip.metadb"; const mmdbFileName = "geoip.metadb";
const asnFileName = "ASN.mmdb";
const geoIpFileName = "GeoIP.dat";
const geoSiteFileName = "GeoSite.dat"; const geoSiteFileName = "GeoSite.dat";
final double kHeaderHeight = system.isDesktop ? (Platform.isMacOS ? 28 : 40) : 0; const asnFileName = "ASN.mmdb";
const GeoXMap defaultGeoXMap = {
"mmdb":
"https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.metadb",
"asn":
"https://github.com/xishang0128/geoip/releases/download/latest/GeoLite2-ASN.mmdb",
"geoip":
"https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/GeoIP.dat",
"geosite":
"https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geosite.dat"
};
const profilesDirectoryName = "profiles"; const profilesDirectoryName = "profiles";
const localhost = "127.0.0.1"; const localhost = "127.0.0.1";
const clashConfigKey = "clash_config"; const clashConfigKey = "clash_config";
@@ -35,22 +18,12 @@ const configKey = "config";
const listItemPadding = EdgeInsets.symmetric(horizontal: 16); const listItemPadding = EdgeInsets.symmetric(horizontal: 16);
const double dialogCommonWidth = 300; const double dialogCommonWidth = 300;
const repository = "chen08209/FlClash"; const repository = "chen08209/FlClash";
const defaultExternalController = "127.0.0.1:9090";
const maxMobileWidth = 600; const maxMobileWidth = 600;
const maxLaptopWidth = 840; const maxLaptopWidth = 840;
const geodataLoaderMemconservative = "memconservative";
const geodataLoaderStandard = "standard";
const defaultTestUrl = "https://www.gstatic.com/generate_204";
final filter = ImageFilter.blur( final filter = ImageFilter.blur(
sigmaX: 5, sigmaX: 5,
sigmaY: 5, sigmaY: 5,
tileMode: TileMode.mirror, tileMode: TileMode.mirror,
); );
const viewModeColumnsMap = {
ViewMode.mobile: [2, 1],
ViewMode.laptop: [3, 2],
ViewMode.desktop: [4, 3],
};
const defaultPrimaryColor = Colors.brown; const defaultPrimaryColor = Colors.brown;

View File

@@ -7,12 +7,8 @@ extension BuildContextExtension on BuildContext {
return findAncestorStateOfType<CommonScaffoldState>(); return findAncestorStateOfType<CommonScaffoldState>();
} }
Size get appSize{
return MediaQuery.of(this).size;
}
double get width { double get width {
return appSize.width; return MediaQuery.of(this).size.width;
} }
ColorScheme get colorScheme => Theme.of(this).colorScheme; ColorScheme get colorScheme => Theme.of(this).colorScheme;

View File

@@ -1,67 +0,0 @@
extension IterableExt<T> on Iterable<T> {
Iterable<T> separated(T separator) sync* {
final iterator = this.iterator;
if (!iterator.moveNext()) return;
yield iterator.current;
while (iterator.moveNext()) {
yield separator;
yield iterator.current;
}
}
Iterable<List<T>> chunks(int size) sync* {
if (length == 0) return;
var iterator = this.iterator;
while (iterator.moveNext()) {
var chunk = [iterator.current];
for (var i = 1; i < size && iterator.moveNext(); i++) {
chunk.add(iterator.current);
}
yield chunk;
}
}
Iterable<T> fill(
int length, {
required T Function(int count) filler,
}) sync* {
int count = 0;
for (var item in this) {
yield item;
count++;
if (count >= length) return;
}
while (count < length) {
yield filler(count);
count++;
}
}
}
extension DoubleListExt on List<double> {
int findInterval(num target) {
if (isEmpty) return -1;
if (target < first) return -1;
if (target >= last) return length - 1;
int left = 0;
int right = length - 1;
while (left <= right) {
int mid = left + (right - left) ~/ 2;
if (mid == length - 1 ||
(this[mid] <= target && target < this[mid + 1])) {
return mid;
} else if (target < this[mid]) {
right = mid - 1;
} else {
left = mid + 1;
}
}
return -1; // 这行理论上不会执行到,但为了完整性保留
}
}

View File

@@ -29,20 +29,6 @@ class Navigation {
label: "profiles", label: "profiles",
fragment: ProfilesFragment(), fragment: ProfilesFragment(),
), ),
const NavigationItem(
icon: Icon(Icons.view_timeline),
label: "requests",
fragment: RequestsFragment(),
description: "requestsDesc",
modes: [NavigationItemMode.desktop, NavigationItemMode.more],
),
const NavigationItem(
icon: Icon(Icons.ballot),
label: "connections",
fragment: ConnectionsFragment(),
description: "connectionsDesc",
modes: [NavigationItemMode.desktop, NavigationItemMode.more],
),
const NavigationItem( const NavigationItem(
icon: Icon(Icons.swap_vert_circle), icon: Icon(Icons.swap_vert_circle),
label: "resources", label: "resources",

View File

@@ -2,7 +2,6 @@ import 'dart:io';
import 'dart:isolate'; import 'dart:isolate';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:fl_clash/common/app_localizations.dart';
import 'package:fl_clash/common/constant.dart'; import 'package:fl_clash/common/constant.dart';
import 'package:fl_clash/enum/enum.dart'; import 'package:fl_clash/enum/enum.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -40,9 +39,6 @@ class Other {
} }
final diff = timeStamp / 1000; final diff = timeStamp / 1000;
final inHours = (diff / 3600).floor(); final inHours = (diff / 3600).floor();
if (inHours > 99) {
return "99:59:59";
}
final inMinutes = (diff / 60 % 60).floor(); final inMinutes = (diff / 60 % 60).floor();
final inSeconds = (diff % 60).floor(); final inSeconds = (diff % 60).floor();
@@ -101,9 +97,9 @@ class Other {
String getTrayIconPath() { String getTrayIconPath() {
if (Platform.isWindows) { if (Platform.isWindows) {
return "assets/images/icon.ico"; return "assets/images/app_icon.ico";
} else { } else {
return "assets/images/icon_monochrome.png"; return "assets/images/launch_icon.png";
} }
} }
@@ -175,7 +171,7 @@ class Other {
} }
List<String> parseReleaseBody(String? body) { List<String> parseReleaseBody(String? body) {
if (body == null) return []; if(body == null) return [];
const pattern = r'- (.+?)\. \[.+?\]'; const pattern = r'- (.+?)\. \[.+?\]';
final regex = RegExp(pattern); final regex = RegExp(pattern);
return regex return regex
@@ -185,29 +181,11 @@ class Other {
.toList(); .toList();
} }
ViewMode getViewMode(double viewWidth) { ViewMode getViewMode(double viewWidth){
if (viewWidth <= maxMobileWidth) return ViewMode.mobile; if (viewWidth <= maxMobileWidth) return ViewMode.mobile;
if (viewWidth <= maxLaptopWidth) return ViewMode.laptop; if (viewWidth <= maxLaptopWidth) return ViewMode.laptop;
return ViewMode.desktop; return ViewMode.desktop;
} }
int getColumns(ViewMode viewMode, int currentColumns) {
final targetColumnsArray = viewModeColumnsMap[viewMode]!;
if (targetColumnsArray.contains(currentColumns)) {
return currentColumns;
}
return targetColumnsArray.first;
}
String getColumnsTextForInt(int number){
return switch(number){
1 => appLocalizations.oneColumn,
2 => appLocalizations.twoColumns,
3 => appLocalizations.threeColumns,
4 => appLocalizations.fourColumns,
int() => throw UnimplementedError(),
};
}
} }
final other = Other(); final other = Other();

View File

@@ -1,13 +1,22 @@
import 'dart:io'; import 'dart:async';
import 'package:package_info_plus/package_info_plus.dart'; import 'package:package_info_plus/package_info_plus.dart';
import 'common.dart'; class AppPackage{
extension PackageInfoExtension on PackageInfo { static AppPackage? _instance;
String get ua => [ Completer<PackageInfo> packageInfoCompleter = Completer();
"$appName/v$version",
"clash-verge", AppPackage._internal() {
"Platform/${Platform.operatingSystem}", PackageInfo.fromPlatform().then(
].join(" "); (value) => packageInfoCompleter.complete(value),
);
}
factory AppPackage() {
_instance ??= AppPackage._internal();
return _instance!;
}
} }
final appPackage = AppPackage();

View File

@@ -8,30 +8,12 @@ import 'constant.dart';
class AppPath { class AppPath {
static AppPath? _instance; static AppPath? _instance;
Completer<Directory> cacheDir = Completer(); Completer<Directory> applicationSupportDirectoryCompleter = Completer();
// Future<Directory> _createDesktopCacheDir() async {
// final path = join(dirname(Platform.resolvedExecutable), 'cache');
// final dir = Directory(path);
// if (await dir.exists()) {
// await dir.create(recursive: true);
// }
// return dir;
// }
AppPath._internal() { AppPath._internal() {
getApplicationSupportDirectory().then((value) { getApplicationSupportDirectory().then(
cacheDir.complete(value); (value) => applicationSupportDirectoryCompleter.complete(value),
}); );
// if (Platform.isAndroid) {
// getApplicationSupportDirectory().then((value) {
// cacheDir.complete(value);
// });
// } else {
// _createDesktopCacheDir().then((value) {
// cacheDir.complete(value);
// });
// }
} }
factory AppPath() { factory AppPath() {
@@ -40,12 +22,12 @@ class AppPath {
} }
Future<String> getHomeDirPath() async { Future<String> getHomeDirPath() async {
final directory = await cacheDir.future; final directory = await applicationSupportDirectoryCompleter.future;
return directory.path; return directory.path;
} }
Future<String> getProfilesPath() async { Future<String> getProfilesPath() async {
final directory = await cacheDir.future; final directory = await applicationSupportDirectoryCompleter.future;
return join(directory.path, profilesDirectoryName); return join(directory.path, profilesDirectoryName);
} }

View File

@@ -1,22 +1,30 @@
import 'dart:io';
import 'package:file_picker/file_picker.dart'; import 'package:file_picker/file_picker.dart';
import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/common/common.dart';
import 'package:image_picker/image_picker.dart'; import 'package:image_picker/image_picker.dart';
class Picker { class Picker {
Future<PlatformFile?> pickerConfigFile() async { Future<PlatformFile?> pickerConfigFile() async {
final filePickerResult = await FilePicker.platform.pickFiles( FilePickerResult? filePickerResult;
withData: true, if (Platform.isAndroid) {
allowMultiple: false, filePickerResult = await FilePicker.platform.pickFiles(
); withData: true,
return filePickerResult?.files.first; type: FileType.custom,
} allowedExtensions: ['txt', 'conf'],
);
Future<PlatformFile?> pickerGeoDataFile() async { } else {
final filePickerResult = await FilePicker.platform.pickFiles( filePickerResult = await FilePicker.platform.pickFiles(
withData: true, withData: true,
allowMultiple: false, type: FileType.custom,
); allowedExtensions: ['yaml', 'txt', 'conf'],
return filePickerResult?.files.first; );
}
final file = filePickerResult?.files.first;
if (file == null) {
return null;
}
return file;
} }
Future<String?> pickerConfigQRCode() async { Future<String?> pickerConfigQRCode() async {

View File

@@ -15,8 +15,8 @@ class ProxyManager {
DateTime? get startTime => _proxy.startTime; DateTime? get startTime => _proxy.startTime;
Future<bool?> startProxy({required int port}) async { Future<bool?> startProxy({required int port, String? args}) async {
return await _proxy.startProxy(port); return await _proxy.startProxy(port, args);
} }
Future<bool?> stopProxy() async { Future<bool?> stopProxy() async {
@@ -28,6 +28,20 @@ class ProxyManager {
return await (_proxy as Proxy).updateStartTime(); return await (_proxy as Proxy).updateStartTime();
} }
Future<bool?> setProtect(int fd) async {
if (_proxy is! Proxy) return null;
return await (_proxy as Proxy).setProtect(fd);
}
Future<bool?> startForeground({
required String title,
required String content,
}) async {
if (_proxy is! Proxy) return null;
return await (_proxy as Proxy)
.startForeground(title: title, content: content);
}
factory ProxyManager() { factory ProxyManager() {
_instance ??= ProxyManager._internal(); _instance ??= ProxyManager._internal();
return _instance!; return _instance!;

View File

@@ -12,21 +12,20 @@ class Request {
bool _isStart = false; bool _isStart = false;
Request() { Request() {
_dio = Dio(); _dio = Dio(
_dio.options = BaseOptions( BaseOptions(
headers: {"User-Agent": globalState.appController.clashConfig.globalUa}, headers: {"User-Agent": coreName},
);
_dio.interceptors.add(
InterceptorsWrapper(
onRequest: (options, handler) {
_updateAdapter();
return handler.next(options); // 继续请求
},
), ),
); );
_dio.interceptors.add(InterceptorsWrapper(
onRequest: (options, handler) {
_syncProxy();
return handler.next(options); // 继续请求
},
));
} }
_updateAdapter() { _syncProxy() {
final port = globalState.appController.clashConfig.mixedPort; final port = globalState.appController.clashConfig.mixedPort;
final isStart = globalState.appController.appState.isStart; final isStart = globalState.appController.appState.isStart;
if (_port != port || isStart != _isStart) { if (_port != port || isStart != _isStart) {
@@ -36,13 +35,11 @@ class Request {
createHttpClient: () { createHttpClient: () {
final client = HttpClient(); final client = HttpClient();
if (!_isStart) return client; if (!_isStart) return client;
client.userAgent = globalState.appController.clashConfig.globalUa;
client.findProxy = (url) { client.findProxy = (url) {
return "PROXY localhost:$_port;DIRECT"; return "PROXY localhost:$_port;DIRECT";
}; };
return client; return client;
}, },
validateCertificate: (_, __, ___) => true,
); );
} }
} }
@@ -71,7 +68,8 @@ class Request {
if (response.statusCode != 200) return null; if (response.statusCode != 200) return null;
final data = response.data as Map<String, dynamic>; final data = response.data as Map<String, dynamic>;
final remoteVersion = data['tag_name']; final remoteVersion = data['tag_name'];
final version = globalState.packageInfo.version; final packageInfo = await appPackage.packageInfoCompleter.future;
final version = packageInfo.version;
final hasUpdate = final hasUpdate =
other.compareVersions(remoteVersion.replaceAll('v', ''), version) > 0; other.compareVersions(remoteVersion.replaceAll('v', ''), version) > 0;
if (!hasUpdate) return null; if (!hasUpdate) return null;
@@ -85,7 +83,7 @@ class Request {
"https://ipinfo.io/json/": IpInfo.fromIpInfoIoJson, "https://ipinfo.io/json/": IpInfo.fromIpInfoIoJson,
}; };
Future<IpInfo?> checkIp({CancelToken? cancelToken}) async { Future<IpInfo?> checkIp(CancelToken? cancelToken) async {
for (final source in _ipInfoSources.entries) { for (final source in _ipInfoSources.entries) {
try { try {
final response = await _dio final response = await _dio

View File

@@ -1,27 +0,0 @@
import 'dart:ui';
import 'package:fl_clash/common/common.dart';
import 'package:flutter/material.dart';
class BaseScrollBehavior extends MaterialScrollBehavior {
@override
Set<PointerDeviceKind> get dragDevices => {
PointerDeviceKind.touch,
PointerDeviceKind.stylus,
PointerDeviceKind.invertedStylus,
PointerDeviceKind.trackpad,
if (system.isDesktop) PointerDeviceKind.mouse,
PointerDeviceKind.unknown,
};
}
class HiddenBarScrollBehavior extends BaseScrollBehavior {
@override
Widget buildScrollbar(
BuildContext context,
Widget child,
ScrollableDetails details,
) {
return child;
}
}

View File

@@ -1,110 +0,0 @@
import 'dart:ffi';
import 'dart:io';
import 'package:ffi/ffi.dart';
import 'package:win32/win32.dart';
typedef CreateServiceNative = IntPtr Function(
IntPtr hSCManager,
Pointer<Utf16> lpServiceName,
Pointer<Utf16> lpDisplayName,
Uint32 dwDesiredAccess,
Uint32 dwServiceType,
Uint32 dwStartType,
Uint32 dwErrorControl,
Pointer<Utf16> lpBinaryPathName,
Pointer<Utf16> lpLoadOrderGroup,
Pointer<Uint32> lpdwTagId,
Pointer<Utf16> lpDependencies,
Pointer<Utf16> lpServiceStartName,
Pointer<Utf16> lpPassword,
);
typedef CreateServiceDart = int Function(
int hSCManager,
Pointer<Utf16> lpServiceName,
Pointer<Utf16> lpDisplayName,
int dwDesiredAccess,
int dwServiceType,
int dwStartType,
int dwErrorControl,
Pointer<Utf16> lpBinaryPathName,
Pointer<Utf16> lpLoadOrderGroup,
Pointer<Uint32> lpdwTagId,
Pointer<Utf16> lpDependencies,
Pointer<Utf16> lpServiceStartName,
Pointer<Utf16> lpPassword,
);
const _SERVICE_ALL_ACCESS = 0xF003F;
const _SERVICE_WIN32_OWN_PROCESS = 0x00000010;
const _SERVICE_AUTO_START = 0x00000002;
const _SERVICE_ERROR_NORMAL = 0x00000001;
typedef GetLastErrorNative = Uint32 Function();
typedef GetLastErrorDart = int Function();
class Service {
static Service? _instance;
late DynamicLibrary _advapi32;
Service._internal() {
_advapi32 = DynamicLibrary.open('advapi32.dll');
}
factory Service() {
_instance ??= Service._internal();
return _instance!;
}
Future<void> createService() async {
final int scManager = OpenSCManager(nullptr, nullptr, _SERVICE_ALL_ACCESS);
if (scManager == 0) return;
final serviceName = 'FlClash Service'.toNativeUtf16();
final displayName = 'FlClash Service'.toNativeUtf16();
final binaryPathName = "C:\\Application\\Clash.Verge_1.6.6_x64_portable\\resources\\clash-verge-service.exe".toNativeUtf16();
final createService =
_advapi32.lookupFunction<CreateServiceNative, CreateServiceDart>(
'CreateServiceW',
);
final getLastError = DynamicLibrary.open('kernel32.dll')
.lookupFunction<GetLastErrorNative, GetLastErrorDart>('GetLastError');
final serviceHandle = createService(
scManager,
serviceName,
displayName,
_SERVICE_ALL_ACCESS,
_SERVICE_WIN32_OWN_PROCESS,
_SERVICE_AUTO_START,
_SERVICE_ERROR_NORMAL,
binaryPathName,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
);
print("serviceHandle $serviceHandle");
final errorCode = GetLastError();
print('Error code: $errorCode');
final result = StartService(serviceHandle, 0, nullptr);
if (result == 0) {
print('Failed to start the service.');
} else {
print('Service started successfully.');
}
calloc.free(serviceName);
calloc.free(displayName);
calloc.free(binaryPathName);
}
}
final service = Platform.isWindows ? Service() : null;

View File

@@ -1,5 +1,10 @@
extension StringExtension on String { extension StringExtension on String {
bool get isUrl { bool get isUrl {
return RegExp(r'^(http|https|ftp)://').hasMatch(this); try {
Uri.parse(this);
return true;
} catch (e) {
return false;
}
} }
} }

View File

@@ -2,13 +2,20 @@ import 'package:flutter/material.dart';
import 'color.dart'; import 'color.dart';
extension TextStyleExtension on TextStyle { extension TextStyleExtension on TextStyle {
TextStyle get toLight => copyWith(color: color?.toLight()); toLight() {
return copyWith(color: color?.toLight());
}
TextStyle get toLighter => copyWith(color: color?.toLighter()); toLighter() {
return copyWith(color: color?.toLighter());
}
TextStyle get toSoftBold => copyWith(fontWeight: FontWeight.w500);
TextStyle get toBold => copyWith(fontWeight: FontWeight.bold); toSoftBold() {
return copyWith(fontWeight: FontWeight.w500);
}
TextStyle get toMinus => copyWith(fontSize: fontSize! - 2); toBold() {
} return copyWith(fontWeight: FontWeight.bold);
}
}

View File

@@ -1,6 +1,5 @@
import 'dart:io'; import 'dart:io';
import 'package:fl_clash/models/config.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:window_manager/window_manager.dart'; import 'package:window_manager/window_manager.dart';
import 'package:windows_single_instance/windows_single_instance.dart'; import 'package:windows_single_instance/windows_single_instance.dart';
@@ -9,7 +8,7 @@ import 'protocol.dart';
import 'system.dart'; import 'system.dart';
class Window { class Window {
init(WindowProps props) async { init() async {
if (Platform.isWindows) { if (Platform.isWindows) {
await WindowsSingleInstance.ensureSingleInstance([], "FlClash"); await WindowsSingleInstance.ensureSingleInstance([], "FlClash");
protocol.register("clash"); protocol.register("clash");
@@ -17,21 +16,11 @@ class Window {
protocol.register("flclash"); protocol.register("flclash");
} }
await windowManager.ensureInitialized(); await windowManager.ensureInitialized();
WindowOptions windowOptions = WindowOptions( WindowOptions windowOptions = const WindowOptions(
size: Size(props.width, props.height), size: Size(1000, 600),
minimumSize: const Size(380, 500), minimumSize: Size(400, 600),
titleBarStyle: TitleBarStyle.hidden, center: true,
); );
if (props.left != null || props.top != null) {
await windowManager.setPosition(
Offset(props.left ?? 0, props.top ?? 0),
);
} else {
await windowManager.setAlignment(Alignment.center);
}
// if(Platform.isWindows){
// await windowManager.setTitleBarStyle(TitleBarStyle.hidden);
// }
await windowManager.waitUntilReadyToShow(windowOptions, () async { await windowManager.waitUntilReadyToShow(windowOptions, () async {
await windowManager.setPreventClose(true); await windowManager.setPreventClose(true);
}); });

View File

@@ -1,5 +1,4 @@
import 'dart:async'; import 'dart:async';
import 'dart:io';
import 'package:fl_clash/enum/enum.dart'; import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/state.dart'; import 'package:fl_clash/state.dart';
@@ -18,8 +17,6 @@ class AppController {
late ClashConfig clashConfig; late ClashConfig clashConfig;
late Measure measure; late Measure measure;
late Function updateClashConfigDebounce; late Function updateClashConfigDebounce;
late Function updateGroupDebounce;
late Function addCheckIpNumDebounce;
AppController(this.context) { AppController(this.context) {
appState = context.read<AppState>(); appState = context.read<AppState>();
@@ -28,19 +25,12 @@ class AppController {
updateClashConfigDebounce = debounce<Function()>(() async { updateClashConfigDebounce = debounce<Function()>(() async {
await updateClashConfig(); await updateClashConfig();
}); });
addCheckIpNumDebounce = debounce(() {
appState.checkIpNum++;
});
updateGroupDebounce = debounce(() async {
await updateGroups();
});
measure = Measure.of(context); measure = Measure.of(context);
} }
Future<void> updateSystemProxy(bool isStart) async { Future<void> updateSystemProxy(bool isStart) async {
if (isStart) { if (isStart) {
await globalState.startSystemProxy( await globalState.startSystemProxy(
appState: appState,
config: config, config: config,
clashConfig: clashConfig, clashConfig: clashConfig,
); );
@@ -50,15 +40,10 @@ class AppController {
updateRunTime, updateRunTime,
updateTraffic, updateTraffic,
]; ];
if (Platform.isAndroid) return;
await applyProfile(isPrue: true);
} else { } else {
await globalState.stopSystemProxy(); await globalState.stopSystemProxy();
clashCore.resetTraffic();
appState.traffics = []; appState.traffics = [];
appState.totalTraffic = Traffic();
appState.runTime = null; appState.runTime = null;
addCheckIpNumDebounce();
} }
} }
@@ -78,10 +63,19 @@ class AppController {
updateTraffic() { updateTraffic() {
globalState.updateTraffic( globalState.updateTraffic(
config: config,
appState: appState, appState: appState,
); );
} }
changeProxy() {
globalState.changeProxy(
appState: appState,
config: config,
clashConfig: clashConfig,
);
}
addProfile(Profile profile) async { addProfile(Profile profile) async {
config.setProfile(profile); config.setProfile(profile);
if (config.currentProfileId != null) return; if (config.currentProfileId != null) return;
@@ -98,14 +92,18 @@ class AppController {
final updateId = config.profiles.first.id; final updateId = config.profiles.first.id;
changeProfile(updateId); changeProfile(updateId);
} else { } else {
updateSystemProxy(false); changeProfile(null);
} }
} }
} }
Future<void> updateProfile(Profile profile) async { Future<void> updateProfile(String id) async {
await profile.update(); final profile = config.getCurrentProfileForId(id);
config.setProfile(await profile.update()); if (profile != null) {
final tempProfile = profile.copyWith();
await tempProfile.update();
config.setProfile(tempProfile);
}
} }
Future<void> updateClashConfig({bool isPatch = true}) async { Future<void> updateClashConfig({bool isPatch = true}) async {
@@ -116,25 +114,16 @@ class AppController {
); );
} }
Future applyProfile({bool isPrue = false}) async { Future applyProfile() async {
if (isPrue) { final commonScaffoldState = globalState.homeScaffoldKey.currentState;
if (commonScaffoldState?.mounted != true) return;
commonScaffoldState?.loadingRun(() async {
await globalState.applyProfile( await globalState.applyProfile(
appState: appState, appState: appState,
config: config, config: config,
clashConfig: clashConfig, clashConfig: clashConfig,
); );
} else { });
final commonScaffoldState = globalState.homeScaffoldKey.currentState;
if (commonScaffoldState?.mounted != true) return;
await commonScaffoldState?.loadingRun(() async {
await globalState.applyProfile(
appState: appState,
config: config,
clashConfig: clashConfig,
);
});
}
addCheckIpNumDebounce();
} }
changeProfile(String? value) async { changeProfile(String? value) async {
@@ -156,16 +145,7 @@ class AppController {
if (isNotNeedUpdate == false || profile.type == ProfileType.file) { if (isNotNeedUpdate == false || profile.type == ProfileType.file) {
continue; continue;
} }
try { await updateProfile(profile.id);
updateProfile(profile);
} catch (e) {
appState.addLog(
Log(
logLevel: LogLevel.info,
payload: e.toString(),
),
);
}
} }
} }
@@ -174,7 +154,7 @@ class AppController {
if (profile.type == ProfileType.file) { if (profile.type == ProfileType.file) {
continue; continue;
} }
await updateProfile(profile); await updateProfile(profile.id);
} }
} }
@@ -201,18 +181,6 @@ class AppController {
await preferences.saveClashConfig(clashConfig); await preferences.saveClashConfig(clashConfig);
} }
changeProxy({
required String groupName,
required String proxyName,
}) {
globalState.changeProxy(
config: config,
groupName: groupName,
proxyName: proxyName,
);
addCheckIpNumDebounce();
}
handleBackOrExit() async { handleBackOrExit() async {
if (config.isMinimizeOnExit) { if (config.isMinimizeOnExit) {
if (system.isDesktop) { if (system.isDesktop) {
@@ -254,21 +222,20 @@ class AppController {
final tagName = data['tag_name']; final tagName = data['tag_name'];
final body = data['body']; final body = data['body'];
final submits = other.parseReleaseBody(body); final submits = other.parseReleaseBody(body);
final textTheme = context.textTheme;
globalState.showMessage( globalState.showMessage(
title: appLocalizations.discoverNewVersion, title: appLocalizations.discoverNewVersion,
message: TextSpan( message: TextSpan(
text: "$tagName \n", text: "$tagName \n",
style: textTheme.headlineSmall, style: context.textTheme.headlineSmall,
children: [ children: [
TextSpan( TextSpan(
text: "\n", text: "\n",
style: textTheme.bodyMedium, style: context.textTheme.bodyMedium,
), ),
for (final submit in submits) for (final submit in submits)
TextSpan( TextSpan(
text: "- $submit \n", text: "- $submit \n",
style: textTheme.bodyMedium, style: context.textTheme.bodyMedium,
), ),
], ],
), ),
@@ -289,38 +256,18 @@ class AppController {
} }
} }
init() async { afterInit() async {
if (config.autoRun) {
await updateSystemProxy(true);
} else {
await proxyManager.updateStartTime();
await updateSystemProxy(proxyManager.isStart);
}
autoUpdateProfiles();
updateLogStatus(); updateLogStatus();
if (!config.silentLaunch) { if (!config.silentLaunch) {
window?.show(); window?.show();
} }
final commonScaffoldState = globalState.homeScaffoldKey.currentState;
if (commonScaffoldState?.mounted == true) {
await commonScaffoldState?.loadingRun(() async {
await globalState.applyProfile(
appState: appState,
config: config,
clashConfig: clashConfig,
);
}, title: appLocalizations.init);
} else {
await globalState.applyProfile(
appState: appState,
config: config,
clashConfig: clashConfig,
);
}
await afterInit();
}
afterInit() async {
await proxyManager.updateStartTime();
if (proxyManager.isStart) {
await updateSystemProxy(true);
} else {
await updateSystemProxy(config.autoRun);
}
autoUpdateProfiles();
autoCheckUpdate(); autoCheckUpdate();
} }
@@ -383,17 +330,17 @@ class AppController {
} }
addProfileFormURL(String url) async { addProfileFormURL(String url) async {
if (globalState.navigatorKey.currentState?.canPop() ?? false) { globalState.navigatorKey.currentState?.popUntil((route) => route.isFirst);
globalState.navigatorKey.currentState?.popUntil((route) => route.isFirst);
}
toProfiles(); toProfiles();
final commonScaffoldState = globalState.homeScaffoldKey.currentState; final commonScaffoldState = globalState.homeScaffoldKey.currentState;
if (commonScaffoldState?.mounted != true) return; if (commonScaffoldState?.mounted != true) return;
final profile = await commonScaffoldState?.loadingRun<Profile>( final profile = await commonScaffoldState?.loadingRun<Profile>(
() async { () async {
return await Profile.normal( final profile = Profile(
url: url, url: url,
).update(); );
await profile.update();
return profile;
}, },
title: "${appLocalizations.add}${appLocalizations.profile}", title: "${appLocalizations.add}${appLocalizations.profile}",
); );
@@ -404,10 +351,6 @@ class AppController {
addProfileFormFile() async { addProfileFormFile() async {
final platformFile = await globalState.safeRun(picker.pickerConfigFile); final platformFile = await globalState.safeRun(picker.pickerConfigFile);
final bytes = platformFile?.bytes;
if (bytes == null) {
return null;
}
if (!context.mounted) return; if (!context.mounted) return;
globalState.navigatorKey.currentState?.popUntil((route) => route.isFirst); globalState.navigatorKey.currentState?.popUntil((route) => route.isFirst);
toProfiles(); toProfiles();
@@ -416,7 +359,13 @@ class AppController {
final profile = await commonScaffoldState?.loadingRun<Profile?>( final profile = await commonScaffoldState?.loadingRun<Profile?>(
() async { () async {
await Future.delayed(const Duration(milliseconds: 300)); await Future.delayed(const Duration(milliseconds: 300));
return await Profile.normal(label: platformFile?.name).saveFile(bytes); final bytes = platformFile?.bytes;
if (bytes == null) {
return null;
}
final profile = Profile(label: platformFile?.name);
await profile.saveFile(bytes);
return profile;
}, },
title: "${appLocalizations.add}${appLocalizations.profile}", title: "${appLocalizations.add}${appLocalizations.profile}",
); );
@@ -431,53 +380,9 @@ class AppController {
addProfileFormURL(url); addProfileFormURL(url);
} }
int get columns => other.getColumns(appState.viewMode, config.proxiesColumns);
updateViewWidth(double width) { updateViewWidth(double width) {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
appState.viewWidth = width; appState.viewWidth = width;
}); });
} }
List<Proxy> _sortOfName(List<Proxy> proxies) {
return List.of(proxies)
..sort(
(a, b) => other.sortByChar(a.name, b.name),
);
}
List<Proxy> _sortOfDelay(List<Proxy> proxies) {
return proxies = List.of(proxies)
..sort(
(a, b) {
final aDelay = appState.getDelay(a.name);
final bDelay = appState.getDelay(b.name);
if (aDelay == null && bDelay == null) {
return 0;
}
if (aDelay == null || aDelay == -1) {
return 1;
}
if (bDelay == null || bDelay == -1) {
return -1;
}
return aDelay.compareTo(bDelay);
},
);
}
List<Proxy> getSortProxies(List<Proxy> proxies) {
return switch (config.proxiesSortType) {
ProxiesSortType.none => proxies,
ProxiesSortType.delay => _sortOfDelay(proxies),
ProxiesSortType.name => _sortOfName(proxies),
};
}
String getCurrentSelectedName(String groupName) {
final group = appState.getGroupWithName(groupName);
return group?.getCurrentSelectedName(
config.currentSelectedMap[groupName] ?? '') ??
'';
}
} }

View File

@@ -1,6 +1,6 @@
// ignore_for_file: constant_identifier_names // ignore_for_file: constant_identifier_names
enum GroupType { Selector, URLTest, Fallback, LoadBalance, Relay } enum GroupType { Selector, URLTest, Fallback }
enum GroupName { GLOBAL, Proxy, Auto, Fallback } enum GroupName { GLOBAL, Proxy, Auto, Fallback }
@@ -56,32 +56,9 @@ enum ProfileType { file, url }
enum ResultType { success, error } enum ResultType { success, error }
enum AppMessageType { enum MessageType { log, tun, delay, process, now }
log,
delay,
request,
started,
loaded,
}
enum ServiceMessageType {
protect,
process,
started,
loaded,
}
enum FindProcessMode { always, off }
enum RecoveryOption { enum RecoveryOption {
all, all,
onlyProfiles, onlyProfiles,
} }
enum ChipType { action, delete }
enum CommonCardType { plain, filled }
enum ProxiesType { tab, list }
enum ProxyCardType { expand, shrink, min }

View File

@@ -1,20 +1,8 @@
import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/state.dart'; import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/list.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:package_info_plus/package_info_plus.dart';
@immutable import 'package:url_launcher/url_launcher.dart';
class Contributor {
final String avatar;
final String name;
final String link;
const Contributor({
required this.avatar,
required this.name,
required this.link,
});
}
class AboutFragment extends StatelessWidget { class AboutFragment extends StatelessWidget {
const AboutFragment({super.key}); const AboutFragment({super.key});
@@ -22,7 +10,8 @@ class AboutFragment extends StatelessWidget {
_checkUpdate(BuildContext context) async { _checkUpdate(BuildContext context) async {
final commonScaffoldState = context.commonScaffoldState; final commonScaffoldState = context.commonScaffoldState;
if (commonScaffoldState?.mounted != true) return; if (commonScaffoldState?.mounted != true) return;
final data = await commonScaffoldState?.loadingRun<Map<String, dynamic>?>( final data =
await commonScaffoldState?.loadingRun<Map<String, dynamic>?>(
request.checkForUpdate, request.checkForUpdate,
title: appLocalizations.checkUpdate, title: appLocalizations.checkUpdate,
); );
@@ -32,40 +21,91 @@ class AboutFragment extends StatelessWidget {
); );
} }
List<Widget> _buildMoreSection(BuildContext context) { @override
return generateSection( Widget build(BuildContext context) {
separated: false, return ListView(
title: appLocalizations.more, padding: kMaterialListPadding.copyWith(
items: [ top: 16,
ListItem( bottom: 16,
),
children: [
ListTile(
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Wrap(
spacing: 16,
crossAxisAlignment: WrapCrossAlignment.center,
children: [
Image.asset(
'assets/images/launch_icon.png',
width: 100,
height: 100,
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
appName,
style: Theme.of(context).textTheme.headlineSmall,
),
FutureBuilder<PackageInfo>(
future: appPackage.packageInfoCompleter.future,
builder: (_, package) {
final version = package.data?.version;
if (version == null) return Container();
return Text(
version,
style: Theme.of(context).textTheme.labelLarge,
);
},
)
],
)
],
),
const SizedBox(
height: 24,
),
Text(
appLocalizations.desc,
style: Theme.of(context).textTheme.bodySmall,
),
],
),
),
const SizedBox(
height: 12,
),
ListTile(
title: Text(appLocalizations.checkUpdate), title: Text(appLocalizations.checkUpdate),
onTap: () { onTap: () {
_checkUpdate(context); _checkUpdate(context);
}, },
), ),
ListItem( ListTile(
title: const Text("Telegram"), title: const Text("Telegram"),
onTap: () { onTap: () {
globalState.openUrl( launchUrl(
"https://t.me/+G-veVtwBOl4wODc1", Uri.parse("https://t.me/+G-veVtwBOl4wODc1"),
); );
}, },
trailing: const Icon(Icons.launch), trailing: const Icon(Icons.launch),
), ),
ListItem( ListTile(
title: Text(appLocalizations.project), title: Text(appLocalizations.project),
onTap: () { onTap: () {
globalState.openUrl( launchUrl(
"https://github.com/$repository", Uri.parse("https://github.com/$repository"),
); );
}, },
trailing: const Icon(Icons.launch), trailing: const Icon(Icons.launch),
), ),
ListItem( ListTile(
title: Text(appLocalizations.core), title: Text(appLocalizations.core),
onTap: () { onTap: () {
globalState.openUrl( launchUrl(
"https://github.com/chen08209/Clash.Meta/tree/FlClash", Uri.parse("https://github.com/chen08209/Clash.Meta/tree/FlClash"),
); );
}, },
trailing: const Icon(Icons.launch), trailing: const Icon(Icons.launch),
@@ -73,139 +113,4 @@ class AboutFragment extends StatelessWidget {
], ],
); );
} }
List<Widget> _buildContributorsSection() {
const contributors = [
Contributor(
avatar: "assets/images/avatars/june2.jpg",
name: "June2",
link: "https://t.me/Jibadong",
),
Contributor(
avatar: "assets/images/avatars/arue.jpg",
name: "Arue",
link: "https://t.me/xrcm6868",
),
];
return generateSection(
separated: false,
title: appLocalizations.otherContributors,
items: [
ListItem(
title: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Wrap(
spacing: 24,
children: [
for (final contributor in contributors)
Avatar(
contributor: contributor,
),
],
),
),
)
],
);
}
@override
Widget build(BuildContext context) {
final items = [
ListTile(
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Wrap(
spacing: 16,
crossAxisAlignment: WrapCrossAlignment.center,
children: [
Padding(
padding: const EdgeInsets.all(12),
child: Image.asset(
'assets/images/icon.png',
width: 64,
height: 64,
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
appName,
style: Theme.of(context).textTheme.headlineSmall,
),
Text(
globalState.packageInfo.version,
style: Theme.of(context).textTheme.labelLarge,
)
],
)
],
),
const SizedBox(
height: 24,
),
Text(
appLocalizations.desc,
style: Theme.of(context).textTheme.bodySmall,
),
],
),
),
const SizedBox(
height: 12,
),
..._buildContributorsSection(),
..._buildMoreSection(context),
];
return Padding(
padding: kMaterialListPadding.copyWith(
top: 16,
bottom: 16,
),
child: generateListView(items),
);
}
}
class Avatar extends StatelessWidget {
final Contributor contributor;
const Avatar({
super.key,
required this.contributor,
});
@override
Widget build(BuildContext context) {
return InkWell(
splashColor: Colors.transparent,
highlightColor: Colors.transparent,
hoverColor: Colors.transparent,
child: Column(
children: [
SizedBox(
width: 36,
height: 36,
child: CircleAvatar(
foregroundImage: AssetImage(
contributor.avatar,
),
),
),
const SizedBox(
height: 4,
),
Text(
contributor.name,
style: context.textTheme.bodySmall,
)
],
),
onTap: () {
globalState.openUrl(contributor.link);
},
);
}
} }

View File

@@ -8,13 +8,6 @@ import 'package:fl_clash/widgets/widgets.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
extension AccessControlExtension on AccessControl {
List<String> get currentList => switch (mode) {
AccessControlMode.acceptSelected => acceptList,
AccessControlMode.rejectSelected => rejectList,
};
}
class AccessFragment extends StatefulWidget { class AccessFragment extends StatefulWidget {
const AccessFragment({super.key}); const AccessFragment({super.key});
@@ -35,12 +28,6 @@ class _AccessFragmentState extends State<AccessFragment> {
}); });
} }
@override
void dispose() {
super.dispose();
packagesListenable.dispose();
}
Widget _buildAppProxyModePopup() { Widget _buildAppProxyModePopup() {
final items = [ final items = [
CommonPopupMenuItem( CommonPopupMenuItem(
@@ -96,64 +83,137 @@ class _AccessFragmentState extends State<AccessFragment> {
); );
} }
Widget _buildSearchButton(List<Package> packages) { Widget _buildSelectedAllButton({
return IconButton( required bool isSelectedAll,
tooltip: appLocalizations.search, required List<String> allValueList,
onPressed: () { }) {
showSearch( return Builder(
context: context, builder: (context) {
delegate: AccessControlSearchDelegate( final tooltip = isSelectedAll
packages: packages, ? appLocalizations.cancelSelectAll
), : appLocalizations.selectAll;
).then((_) => {setState(() {})}); return IconButton(
tooltip: tooltip,
onPressed: () {
final config = globalState.appController.config;
final isAccept =
config.accessControl.mode == AccessControlMode.acceptSelected;
if (isSelectedAll) {
config.accessControl = switch (isAccept) {
true => config.accessControl.copyWith(
acceptList: [],
),
false => config.accessControl.copyWith(
rejectList: [],
),
};
} else {
config.accessControl = switch (isAccept) {
true => config.accessControl.copyWith(
acceptList: allValueList,
),
false => config.accessControl.copyWith(
rejectList: allValueList,
),
};
}
},
icon: isSelectedAll
? const Icon(Icons.deselect)
: const Icon(Icons.select_all),
);
}, },
icon: const Icon(Icons.search),
); );
} }
// Widget _buildSelectedAllButton({ Widget _actionHeader({
// required bool isAccessControl, required bool isAccessControl,
// required bool isSelectedAll, required List<String> valueList,
// required List<String> allValueList, required String describe,
// }) { required List<String> packageNameList,
// final tooltip = isSelectedAll }) {
// ? appLocalizations.cancelSelectAll return AbsorbPointer(
// : appLocalizations.selectAll; absorbing: !isAccessControl,
// return AbsorbPointer( child: Padding(
// absorbing: !isAccessControl, padding: const EdgeInsets.only(
// child: FloatingActionButton( top: 4,
// tooltip: tooltip, bottom: 4,
// onPressed: () { left: 16,
// final config = globalState.appController.config; right: 8,
// final isAccept = ),
// config.accessControl.mode == AccessControlMode.acceptSelected; child: Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// if (isSelectedAll) { mainAxisSize: MainAxisSize.max,
// config.accessControl = switch (isAccept) { children: [
// true => config.accessControl.copyWith( Expanded(
// acceptList: [], child: IntrinsicHeight(
// ), child: Column(
// false => config.accessControl.copyWith( mainAxisSize: MainAxisSize.max,
// rejectList: [], crossAxisAlignment: CrossAxisAlignment.start,
// ), children: [
// }; Expanded(
// } else { child: Row(
// config.accessControl = switch (isAccept) { children: [
// true => config.accessControl.copyWith( Flexible(
// acceptList: allValueList, child: Text(
// ), appLocalizations.selected,
// false => config.accessControl.copyWith( style: Theme.of(context)
// rejectList: allValueList, .textTheme
// ), .labelLarge
// }; ?.copyWith(
// } color:
// }, Theme.of(context).colorScheme.primary,
// child: isSelectedAll ),
// ? const Icon(Icons.deselect) ),
// : const Icon(Icons.select_all), ),
// ), const Flexible(
// ); child: SizedBox(
// } width: 8,
),
),
Flexible(
child: Text(
"${valueList.length}",
style: Theme.of(context)
.textTheme
.labelLarge
?.copyWith(
color:
Theme.of(context).colorScheme.primary,
),
),
),
],
),
),
Flexible(
child: Text(describe),
)
],
),
),
),
Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.end,
children: [
Flexible(
child: _buildSelectedAllButton(
isSelectedAll: const ListEquality<String>()
.equals(valueList, packageNameList),
allValueList: packageNameList,
),
),
Flexible(child: _buildFilterSystemAppButton()),
Flexible(child: _buildAppProxyModePopup()),
],
),
],
),
),
);
}
Widget _buildPackageList() { Widget _buildPackageList() {
return ValueListenableBuilder( return ValueListenableBuilder(
@@ -192,11 +252,14 @@ class _AccessFragmentState extends State<AccessFragment> {
accessControlMode == AccessControlMode.acceptSelected accessControlMode == AccessControlMode.acceptSelected
? acceptPackages ? acceptPackages
: rejectPackages; : rejectPackages;
final currentList = accessControl.currentList; final currentList =
accessControlMode == AccessControlMode.acceptSelected
? accessControl.acceptList
: accessControl.rejectList;
final currentPackages = isFilterSystemApp final currentPackages = isFilterSystemApp
? packages ? packages
.where((element) => element.isSystem == false) .where((element) => element.isSystem == false)
.toList() .toList()
: packages; : packages;
final packageNameList = final packageNameList =
currentPackages.map((e) => e.packageName).toList(); currentPackages.map((e) => e.packageName).toList();
@@ -209,82 +272,11 @@ class _AccessFragmentState extends State<AccessFragment> {
status: !isAccessControl, status: !isAccessControl,
child: Column( child: Column(
children: [ children: [
AbsorbPointer( _actionHeader(
absorbing: !isAccessControl, isAccessControl: isAccessControl,
child: Padding( valueList: valueList,
padding: const EdgeInsets.only( describe: describe,
top: 4, packageNameList: packageNameList,
bottom: 4,
left: 16,
right: 8,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisSize: MainAxisSize.max,
children: [
Expanded(
child: IntrinsicHeight(
child: Column(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Row(
children: [
Flexible(
child: Text(
appLocalizations.selected,
style: Theme.of(context)
.textTheme
.labelLarge
?.copyWith(
color: Theme.of(context)
.colorScheme
.primary,
),
),
),
const Flexible(
child: SizedBox(
width: 8,
),
),
Flexible(
child: Text(
"${valueList.length}",
style: Theme.of(context)
.textTheme
.labelLarge
?.copyWith(
color: Theme.of(context)
.colorScheme
.primary,
),
),
),
],
),
),
Flexible(
child: Text(describe),
)
],
),
),
),
Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.end,
children: [
Flexible(
child: _buildSearchButton(currentPackages)),
Flexible(child: _buildFilterSystemAppButton()),
Flexible(child: _buildAppProxyModePopup()),
],
),
],
),
),
), ),
Expanded( Expanded(
flex: 1, flex: 1,
@@ -292,42 +284,42 @@ class _AccessFragmentState extends State<AccessFragment> {
key: const Key("fade_box"), key: const Key("fade_box"),
child: currentPackages.isEmpty child: currentPackages.isEmpty
? const Center( ? const Center(
child: CircularProgressIndicator(), child: CircularProgressIndicator(),
) )
: ListView.builder( : ListView.builder(
itemCount: currentPackages.length, itemCount: currentPackages.length,
itemBuilder: (_, index) { itemBuilder: (_, index) {
final package = currentPackages[index]; final package = currentPackages[index];
return PackageListItem( return PackageListItem(
key: Key(package.packageName), key: Key(package.packageName),
package: package, package: package,
value: value:
valueList.contains(package.packageName), valueList.contains(package.packageName),
isActive: isAccessControl, isActive: isAccessControl,
onChanged: (value) { onChanged: (value) {
if (value == true) { if (value == true) {
valueList.add(package.packageName); valueList.add(package.packageName);
} else { } else {
valueList.remove(package.packageName); valueList.remove(package.packageName);
} }
final config = final config =
globalState.appController.config; globalState.appController.config;
if (accessControlMode == if (accessControlMode ==
AccessControlMode.acceptSelected) { AccessControlMode.acceptSelected) {
config.accessControl = config.accessControl =
config.accessControl.copyWith( config.accessControl.copyWith(
acceptList: valueList, acceptList: valueList,
); );
} else { } else {
config.accessControl = config.accessControl =
config.accessControl.copyWith( config.accessControl.copyWith(
rejectList: valueList, rejectList: valueList,
); );
} }
}, },
); );
}, },
), ),
), ),
), ),
], ],
@@ -437,111 +429,3 @@ class PackageListItem extends StatelessWidget {
); );
} }
} }
class AccessControlSearchDelegate extends SearchDelegate {
final List<Package> packages;
AccessControlSearchDelegate({
required this.packages,
});
List<Package> get _results {
final lowQuery = query.toLowerCase();
return packages
.where(
(package) =>
package.label.toLowerCase().contains(lowQuery) ||
package.packageName.contains(lowQuery),
)
.toList();
}
@override
List<Widget>? buildActions(BuildContext context) {
return [
IconButton(
onPressed: () {
if (query.isEmpty) {
close(context, null);
return;
}
query = '';
},
icon: const Icon(Icons.clear),
),
const SizedBox(
width: 8,
)
];
}
@override
Widget? buildLeading(BuildContext context) {
return IconButton(
onPressed: () {
close(context, null);
},
icon: const Icon(Icons.arrow_back),
);
}
Widget _packageList(List<Package> packages) {
return Selector<Config, PackageListSelectorState>(
selector: (_, config) => PackageListSelectorState(
accessControl: config.accessControl,
isAccessControl: config.isAccessControl,
),
builder: (context, state, __) {
final accessControl = state.accessControl;
final isAccessControl = state.isAccessControl;
final accessControlMode = accessControl.mode;
final currentList = accessControl.currentList;
final packageNameList =
this.packages.map((e) => e.packageName).toList();
final valueList = currentList.intersection(packageNameList);
return DisabledMask(
status: !isAccessControl,
child: ListView.builder(
itemCount: packages.length,
itemBuilder: (_, index) {
final package = packages[index];
return PackageListItem(
key: Key(package.packageName),
package: package,
value: valueList.contains(package.packageName),
isActive: isAccessControl,
onChanged: (value) {
if (value == true) {
valueList.add(package.packageName);
} else {
valueList.remove(package.packageName);
}
final config = globalState.appController.config;
if (accessControlMode == AccessControlMode.acceptSelected) {
config.accessControl = config.accessControl.copyWith(
acceptList: valueList,
);
} else {
config.accessControl = config.accessControl.copyWith(
rejectList: valueList,
);
}
},
);
},
),
);
},
);
}
@override
Widget buildResults(BuildContext context) {
return buildSuggestions(context);
}
@override
Widget buildSuggestions(BuildContext context) {
return _packageList(_results);
}
}

View File

@@ -89,24 +89,6 @@ class ApplicationSettingFragment extends StatelessWidget {
); );
}, },
), ),
if (Platform.isAndroid)
Selector<Config, bool>(
selector: (_, config) => config.isExclude,
builder: (_, isExclude, child) {
return ListItem.switchItem(
leading: const Icon(Icons.visibility_off),
title: Text(appLocalizations.exclude),
subtitle: Text(appLocalizations.excludeDesc),
delegate: SwitchDelegate(
value: isExclude,
onChanged: (value) {
final config = context.read<Config>();
config.isExclude = value;
},
),
);
},
),
if (Platform.isAndroid) if (Platform.isAndroid)
Selector<Config, bool>( Selector<Config, bool>(
selector: (_, config) => config.isAnimateToPage, selector: (_, config) => config.isAnimateToPage,

View File

@@ -6,6 +6,7 @@ import 'package:fl_clash/models/dav.dart';
import 'package:fl_clash/state.dart'; import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/fade_box.dart'; import 'package:fl_clash/widgets/fade_box.dart';
import 'package:fl_clash/widgets/list.dart'; import 'package:fl_clash/widgets/list.dart';
import 'package:fl_clash/widgets/section.dart';
import 'package:fl_clash/widgets/text.dart'; import 'package:fl_clash/widgets/text.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@@ -33,9 +34,9 @@ class _BackupAndRecoveryState extends State<BackupAndRecovery> {
final res = await commonScaffoldState?.loadingRun<bool>(() async { final res = await commonScaffoldState?.loadingRun<bool>(() async {
return await _client?.backup(); return await _client?.backup();
}); });
if (res != true) return; if(res != true) return;
globalState.showMessage( globalState.showMessage(
title: appLocalizations.backup, title: appLocalizations.recovery,
message: TextSpan(text: appLocalizations.backupSuccess), message: TextSpan(text: appLocalizations.backupSuccess),
); );
} }
@@ -45,7 +46,7 @@ class _BackupAndRecoveryState extends State<BackupAndRecovery> {
final res = await commonScaffoldState?.loadingRun<bool>(() async { final res = await commonScaffoldState?.loadingRun<bool>(() async {
return await _client?.recovery(recoveryOption: recoveryOption); return await _client?.recovery(recoveryOption: recoveryOption);
}); });
if (res != true) return; if(res != true) return;
globalState.showMessage( globalState.showMessage(
title: appLocalizations.recovery, title: appLocalizations.recovery,
message: TextSpan(text: appLocalizations.recoverySuccess), message: TextSpan(text: appLocalizations.recoverySuccess),
@@ -68,22 +69,26 @@ class _BackupAndRecoveryState extends State<BackupAndRecovery> {
if (dav == null) { if (dav == null) {
return ListView( return ListView(
children: [ children: [
ListHeader( Section(
title: appLocalizations.account, title: appLocalizations.account,
), child: Builder(
ListItem( builder: (_) {
leading: const Icon(Icons.account_box), return ListItem(
title: Text(appLocalizations.noInfo), leading: const Icon(Icons.account_box),
subtitle: Text(appLocalizations.pleaseBindWebDAV), title: Text(appLocalizations.noInfo),
trailing: FilledButton.tonal( subtitle: Text(appLocalizations.pleaseBindWebDAV),
onPressed: () { trailing: FilledButton.tonal(
_showAddWebDAV(dav); onPressed: () {
_showAddWebDAV(dav);
},
child: Text(
appLocalizations.bind,
),
),
);
}, },
child: Text(
appLocalizations.bind,
),
), ),
), )
], ],
); );
} }
@@ -91,59 +96,62 @@ class _BackupAndRecoveryState extends State<BackupAndRecovery> {
final pingFuture = _client!.pingCompleter.future; final pingFuture = _client!.pingCompleter.future;
return ListView( return ListView(
children: [ children: [
ListHeader(title: appLocalizations.account), Section(
ListItem( title: appLocalizations.account,
leading: const Icon(Icons.account_box), child: ListItem(
title: TooltipText( leading: const Icon(Icons.account_box),
text: Text( title: TooltipText(
dav.user, text: Text(
maxLines: 1, dav.user,
overflow: TextOverflow.ellipsis, maxLines: 1,
overflow: TextOverflow.ellipsis,
),
), ),
), subtitle: Padding(
subtitle: Padding( padding: const EdgeInsets.symmetric(vertical: 4),
padding: const EdgeInsets.symmetric(vertical: 4), child: Row(
child: Row( crossAxisAlignment: CrossAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center, children: [
children: [ Text(appLocalizations.connectivity),
Text(appLocalizations.connectivity), FutureBuilder<bool>(
FutureBuilder<bool>( future: pingFuture,
future: pingFuture, builder: (_, snapshot) {
builder: (_, snapshot) { return Center(
return Center( child: FadeBox(
child: FadeBox( key: const Key("fade_box_1"),
key: const Key("fade_box_1"), child: snapshot.connectionState ==
child: snapshot.connectionState == ConnectionState.waiting ConnectionState.waiting
? const SizedBox( ? const SizedBox(
width: 12, width: 12,
height: 12, height: 12,
child: CircularProgressIndicator( child: CircularProgressIndicator(
strokeWidth: 1, strokeWidth: 1,
),
)
: Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: snapshot.data == true
? Colors.green
: Colors.red,
),
width: 12,
height: 12,
), ),
) ),
: Container( );
decoration: BoxDecoration( },
shape: BoxShape.circle, ),
color: snapshot.data == true ],
? Colors.green ),
: Colors.red,
),
width: 12,
height: 12,
),
),
);
},
),
],
), ),
), trailing: FilledButton.tonal(
trailing: FilledButton.tonal( onPressed: () {
onPressed: () { _showAddWebDAV(dav);
_showAddWebDAV(dav); },
}, child: Text(
child: Text( appLocalizations.edit,
appLocalizations.edit, ),
), ),
), ),
), ),
@@ -153,21 +161,22 @@ class _BackupAndRecoveryState extends State<BackupAndRecovery> {
return FadeBox( return FadeBox(
key: const Key("fade_box_2"), key: const Key("fade_box_2"),
child: snapshot.data == true child: snapshot.data == true
? Column( ? Section(
children: [ title: appLocalizations.backupAndRecovery,
ListHeader( child: Column(
title: appLocalizations.backupAndRecovery), children: [
ListItem( ListItem(
onTap: _backup, onTab: _backup,
title: Text(appLocalizations.backup), title: Text(appLocalizations.backup),
subtitle: Text(appLocalizations.backupDesc), subtitle: Text(appLocalizations.backupDesc),
), ),
ListItem( ListItem(
onTap: _handleRecovery, onTab: _handleRecovery,
title: Text(appLocalizations.recovery), title: Text(appLocalizations.recovery),
subtitle: Text(appLocalizations.recoveryDesc), subtitle: Text(appLocalizations.recoveryDesc),
), ),
], ],
),
) )
: Container(), : Container(),
); );
@@ -219,12 +228,6 @@ class _WebDAVFormDialogState extends State<WebDAVFormDialog> {
Navigator.pop(context); Navigator.pop(context);
} }
@override
void dispose() {
super.dispose();
_obscureController.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return AlertDialog( return AlertDialog(
@@ -340,13 +343,13 @@ class _RecoveryOptionsDialogState extends State<RecoveryOptionsDialog> {
child: Wrap( child: Wrap(
children: [ children: [
ListItem( ListItem(
onTap: () { onTab: () {
_handleOnTab(RecoveryOption.onlyProfiles); _handleOnTab(RecoveryOption.onlyProfiles);
}, },
title: Text(appLocalizations.recoveryProfiles), title: Text(appLocalizations.recoveryProfiles),
), ),
ListItem( ListItem(
onTap: () { onTab: () {
_handleOnTab(RecoveryOption.all); _handleOnTab(RecoveryOption.all);
}, },
title: Text(appLocalizations.recoveryAll), title: Text(appLocalizations.recoveryAll),

View File

@@ -1,5 +1,3 @@
import 'dart:io';
import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart'; import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart'; import 'package:fl_clash/models/models.dart';
@@ -39,443 +37,141 @@ class _ConfigFragmentState extends State<ConfigFragment> {
} }
} }
_showLogLevelDialog(LogLevel value) { _updateLoglevel(LogLevel? logLevel) {
globalState.showCommonDialog( if (logLevel == null ||
child: AlertDialog( logLevel == globalState.appController.clashConfig.logLevel) return;
title: Text(appLocalizations.logLevel), globalState.appController.clashConfig.logLevel = logLevel;
contentPadding: const EdgeInsets.symmetric( globalState.appController.updateClashConfigDebounce();
horizontal: 8,
vertical: 16,
),
content: SizedBox(
width: 250,
child: Wrap(
children: [
for (final logLevel in LogLevel.values)
ListItem.radio(
delegate: RadioDelegate<LogLevel>(
value: logLevel,
groupValue: value,
onChanged: (LogLevel? value) {
if (value == null) {
return;
}
final appController = globalState.appController;
appController.clashConfig.logLevel = value;
appController.updateClashConfigDebounce();
Navigator.of(context).pop();
},
),
title: Text(logLevel.name),
)
],
),
),
),
);
}
_showUaDialog(String? value) {
const uas = [
null,
"clash-verge/v1.6.6",
"ClashforWindows/0.19.23",
];
globalState.showCommonDialog(
child: AlertDialog(
title: const Text("UA"),
contentPadding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 16,
),
content: SizedBox(
width: 250,
child: Wrap(
children: [
for (final ua in uas)
ListItem.radio(
delegate: RadioDelegate<String?>(
value: ua,
groupValue: value,
onChanged: (String? value) {
final appController = globalState.appController;
appController.clashConfig.globalRealUa = value;
appController.updateClashConfigDebounce();
Navigator.of(context).pop();
},
),
title: Text(ua ?? appLocalizations.defaultText),
)
],
),
),
),
);
}
_modifyTestUrl(String testUrl) async {
final newTestUrl = await globalState.showCommonDialog<String>(
child: TestUrlFormDialog(
testUrl: testUrl,
),
);
if (newTestUrl != null && newTestUrl != testUrl && mounted) {
try {
if (!newTestUrl.isUrl) {
throw "Invalid url";
}
globalState.appController.config.testUrl = newTestUrl;
globalState.appController.updateClashConfigDebounce();
} catch (e) {
globalState.showMessage(
title: appLocalizations.testUrl,
message: TextSpan(
text: e.toString(),
),
);
}
}
}
List<Widget> _buildAppSection() {
return generateSection(
title: appLocalizations.app,
items: [
if (Platform.isAndroid)...[
Selector<Config, bool>(
selector: (_, config) => config.allowBypass,
builder: (_, allowBypass, __) {
return ListItem.switchItem(
leading: const Icon(Icons.arrow_forward_outlined),
title: Text(appLocalizations.allowBypass),
subtitle: Text(appLocalizations.allowBypassDesc),
delegate: SwitchDelegate(
value: allowBypass,
onChanged: (bool value) async {
final appController = globalState.appController;
appController.config.allowBypass = value;
},
),
);
},
),
Selector<Config, bool>(
selector: (_, config) => config.systemProxy,
builder: (_, systemProxy, __) {
return ListItem.switchItem(
leading: const Icon(Icons.settings_ethernet),
title: Text(appLocalizations.systemProxy),
subtitle: Text(appLocalizations.systemProxyDesc),
delegate: SwitchDelegate(
value: systemProxy,
onChanged: (bool value) async {
final appController = globalState.appController;
appController.config.systemProxy = value;
},
),
);
},
),
],
Selector<Config, bool>(
selector: (_, config) => config.isCloseConnections,
builder: (_, isCloseConnections, __) {
return ListItem.switchItem(
leading: const Icon(Icons.auto_delete_outlined),
title: Text(appLocalizations.autoCloseConnections),
subtitle: Text(appLocalizations.autoCloseConnectionsDesc),
delegate: SwitchDelegate(
value: isCloseConnections,
onChanged: (bool value) async {
final appController = globalState.appController;
appController.config.isCloseConnections = value;
},
),
);
},
),
Selector<Config, bool>(
selector: (_, config) => config.onlyProxy,
builder: (_, onlyProxy, __) {
return ListItem.switchItem(
leading: const Icon(Icons.data_usage_outlined),
title: Text(appLocalizations.onlyStatisticsProxy),
subtitle: Text(appLocalizations.onlyStatisticsProxyDesc),
delegate: SwitchDelegate(
value: onlyProxy,
onChanged: (bool value) async {
final appController = globalState.appController;
appController.config.onlyProxy = value;
},
),
);
},
),
// Selector<Config, bool>(
// selector: (_, config) => config.isCompatible,
// builder: (_, isCompatible, __) {
// return ListItem.switchItem(
// leading: const Icon(Icons.expand_outlined),
// title: Text(appLocalizations.compatible),
// subtitle: Text(appLocalizations.compatibleDesc),
// delegate: SwitchDelegate(
// value: isCompatible,
// onChanged: (bool value) async {
// final appController = globalState.appController;
// appController.config.isCompatible = value;
// await appController.applyProfile();
// },
// ),
// );
// },
// ),
],
);
}
List<Widget> _buildGeneralSection() {
return generateSection(
title: appLocalizations.general,
items: [
Selector<ClashConfig, LogLevel>(
selector: (_, clashConfig) => clashConfig.logLevel,
builder: (_, value, __) {
return ListItem(
leading: const Icon(Icons.info_outline),
title: Text(appLocalizations.logLevel),
subtitle: Text(value.name),
onTap: () {
_showLogLevelDialog(value);
},
);
},
),
Selector<ClashConfig, String?>(
selector: (_, clashConfig) => clashConfig.globalRealUa,
builder: (_, value, __) {
return ListItem(
leading: const Icon(Icons.computer_outlined),
title: const Text("UA"),
subtitle: Text(value ?? appLocalizations.defaultText),
onTap: () {
_showUaDialog(value);
},
);
},
),
Selector<Config, String>(
selector: (_, config) => config.testUrl,
builder: (_, value, __) {
return ListItem(
leading: const Icon(Icons.timeline),
title: Text(appLocalizations.testUrl),
subtitle: Text(value),
onTap: () {
_modifyTestUrl(value);
},
);
},
),
Selector<ClashConfig, int>(
selector: (_, clashConfig) => clashConfig.mixedPort,
builder: (_, mixedPort, __) {
return ListItem(
onTap: () {
_modifyMixedPort(mixedPort);
},
leading: const Icon(Icons.adjust_outlined),
title: Text(appLocalizations.proxyPort),
subtitle: Text(appLocalizations.proxyPortDesc),
trailing: FilledButton.tonal(
onPressed: () {
_modifyMixedPort(mixedPort);
},
child: Text(
"$mixedPort",
),
),
);
},
),
Selector<ClashConfig, bool>(
selector: (_, clashConfig) => clashConfig.ipv6,
builder: (_, 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 {
final appController = globalState.appController;
appController.clashConfig.ipv6 = value;
appController.updateClashConfigDebounce();
},
),
);
},
),
Selector<ClashConfig, bool>(
selector: (_, clashConfig) => clashConfig.allowLan,
builder: (_, 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 {
final clashConfig = context.read<ClashConfig>();
clashConfig.allowLan = value;
globalState.appController.updateClashConfigDebounce();
},
),
);
},
),
Selector<ClashConfig, bool>(
selector: (_, clashConfig) => clashConfig.unifiedDelay,
builder: (_, 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 {
final appController = globalState.appController;
appController.clashConfig.unifiedDelay = value;
appController.updateClashConfigDebounce();
},
),
);
},
),
Selector<ClashConfig, bool>(
selector: (_, clashConfig) =>
clashConfig.findProcessMode == FindProcessMode.always,
builder: (_, findProcess, __) {
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 {
final appController = globalState.appController;
appController.clashConfig.findProcessMode =
value ? FindProcessMode.always : FindProcessMode.off;
appController.updateClashConfigDebounce();
},
),
);
},
),
Selector<ClashConfig, bool>(
selector: (_, clashConfig) => clashConfig.tcpConcurrent,
builder: (_, tcpConcurrent, __) {
return ListItem.switchItem(
leading: const Icon(Icons.double_arrow_outlined),
title: Text(appLocalizations.tcpConcurrent),
subtitle: Text(appLocalizations.tcpConcurrentDesc),
delegate: SwitchDelegate(
value: tcpConcurrent,
onChanged: (bool value) async {
final appController = globalState.appController;
appController.clashConfig.tcpConcurrent = value;
appController.updateClashConfigDebounce();
},
),
);
},
),
Selector<ClashConfig, bool>(
selector: (_, clashConfig) =>
clashConfig.geodataLoader == geodataLoaderMemconservative,
builder: (_, memconservative, __) {
return ListItem.switchItem(
leading: const Icon(Icons.memory),
title: Text(appLocalizations.geodataLoader),
subtitle: Text(appLocalizations.geodataLoaderDesc),
delegate: SwitchDelegate(
value: memconservative,
onChanged: (bool value) async {
final appController = globalState.appController;
appController.clashConfig.geodataLoader = value
? geodataLoaderMemconservative
: geodataLoaderStandard;
appController.updateClashConfigDebounce();
},
),
);
},
),
Selector<ClashConfig, bool>(
selector: (_, clashConfig) =>
clashConfig.externalController.isNotEmpty,
builder: (_, hasExternalController, __) {
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 {
final appController = globalState.appController;
appController.clashConfig.externalController =
value ? defaultExternalController : '';
appController.updateClashConfigDebounce();
},
),
);
},
),
],
);
}
List<Widget> _buildMoreSection() {
return generateSection(
title: appLocalizations.more,
items: [
if (system.isDesktop)
Selector<ClashConfig, bool>(
selector: (_, clashConfig) => clashConfig.tun.enable,
builder: (_, tunEnable, __) {
return ListItem.switchItem(
leading: const Icon(Icons.important_devices_outlined),
title: Text(appLocalizations.tun),
subtitle: Text(appLocalizations.tunDesc),
delegate: SwitchDelegate(
value: tunEnable,
onChanged: (bool value) async {
final clashConfig = context.read<ClashConfig>();
clashConfig.tun = Tun(enable: value);
globalState.appController.updateClashConfigDebounce();
},
),
);
},
),
],
);
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
List<Widget> items = [ List<Widget> items = [
..._buildAppSection(), Selector<ClashConfig, int>(
..._buildGeneralSection(), selector: (_, clashConfig) => clashConfig.mixedPort,
..._buildMoreSection(), builder: (_, mixedPort, __) {
return ListItem(
onTab: () {
_modifyMixedPort(mixedPort);
},
padding: const EdgeInsets.symmetric(horizontal: 16,vertical: 4),
leading: const Icon(Icons.adjust),
title: Text(appLocalizations.proxyPort),
trailing: FilledButton.tonal(
onPressed: () {
_modifyMixedPort(mixedPort);
},
child: Text(
"$mixedPort",
),
),
);
},
),
Selector<ClashConfig, bool>(
selector: (_, clashConfig) => clashConfig.allowLan,
builder: (_, 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 {
final clashConfig = context.read<ClashConfig>();
clashConfig.allowLan = value;
globalState.appController.updateClashConfigDebounce();
},
),
);
},
),
// if (system.isDesktop)
// Selector<ClashConfig, bool>(
// selector: (_, clashConfig) => clashConfig.tun.enable,
// builder: (_, tunEnable, __) {
// return ListItem.switchItem(
// leading: const Icon(Icons.support),
// title: Text(appLocalizations.tun),
// subtitle: Text(appLocalizations.tunDesc),
// delegate: SwitchDelegate(
// value: tunEnable,
// onChanged: (bool value) async {
// final clashConfig = context.read<ClashConfig>();
// clashConfig.tun = Tun(enable: value);
// globalState.appController.updateClashConfigDebounce();
// },
// ),
// );
// },
// ),
Selector<Config, bool>(
selector: (_, config) => config.isCompatible,
builder: (_, isCompatible, __) {
return ListItem.switchItem(
leading: const Icon(Icons.expand),
title: Text(appLocalizations.compatible),
subtitle: Text(appLocalizations.compatibleDesc),
delegate: SwitchDelegate(
value: isCompatible,
onChanged: (bool value) async {
final appController = globalState.appController;
appController.config.isCompatible = value;
await appController.updateClashConfig(isPatch: false);
await appController.updateGroups();
appController.changeProxy();
},
),
);
},
),
Padding(
padding: kMaterialListPadding,
child: Selector<ClashConfig, LogLevel>(
selector: (_, clashConfig) => clashConfig.logLevel,
builder: (_, value, __) {
return ListItem(
leading: const Icon(Icons.feedback),
title: Text(appLocalizations.logLevel),
trailing: SizedBox(
height: 48,
child: DropdownMenu<LogLevel>(
width: 124,
inputDecorationTheme: const InputDecorationTheme(
filled: true,
contentPadding: EdgeInsets.symmetric(
vertical: 5,
horizontal: 16,
),
),
initialSelection: value,
dropdownMenuEntries: [
for (final logLevel in LogLevel.values)
DropdownMenuEntry<LogLevel>(
value: logLevel,
label: logLevel.name,
)
],
onSelected: _updateLoglevel,
),
),
);
},
),
),
]; ];
return ListView.builder( return ListView.separated(
padding: const EdgeInsets.only(bottom: 32),
itemBuilder: (_, index) { itemBuilder: (_, index) {
return Container( return Container(
alignment: Alignment.center, alignment: Alignment.center,
child: items[index], child: items[index],
); );
}, },
separatorBuilder: (_, __) {
return const Divider(
height: 0,
);
},
itemCount: items.length, itemCount: items.length,
); );
} }
@@ -499,7 +195,7 @@ class _MixedPortFormDialogState extends State<MixedPortFormDialog> {
portController = TextEditingController(text: "${widget.mixedPort}"); portController = TextEditingController(text: "${widget.mixedPort}");
} }
_handleUpdate() async { _handleAddProfileFormURL() async {
final port = portController.value.text; final port = portController.value.text;
if (port.isEmpty) return; if (port.isEmpty) return;
Navigator.of(context).pop<String>(port); Navigator.of(context).pop<String>(port);
@@ -525,64 +221,7 @@ class _MixedPortFormDialogState extends State<MixedPortFormDialog> {
), ),
actions: [ actions: [
TextButton( TextButton(
onPressed: _handleUpdate, onPressed: _handleAddProfileFormURL,
child: Text(appLocalizations.submit),
)
],
);
}
}
class TestUrlFormDialog extends StatefulWidget {
final String testUrl;
const TestUrlFormDialog({
super.key,
required this.testUrl,
});
@override
State<TestUrlFormDialog> createState() => _TestUrlFormDialogState();
}
class _TestUrlFormDialogState extends State<TestUrlFormDialog> {
late TextEditingController testUrlController;
@override
void initState() {
super.initState();
testUrlController = TextEditingController(text: widget.testUrl);
}
_handleUpdate() async {
final testUrl = testUrlController.value.text;
if (testUrl.isEmpty) return;
Navigator.of(context).pop<String>(testUrl);
}
@override
Widget build(BuildContext context) {
return AlertDialog(
title: Text(appLocalizations.testUrl),
content: SizedBox(
width: 300,
child: Wrap(
runSpacing: 16,
children: [
TextField(
maxLines: 5,
minLines: 1,
controller: testUrlController,
decoration: const InputDecoration(
border: OutlineInputBorder(),
),
),
],
),
),
actions: [
TextButton(
onPressed: _handleUpdate,
child: Text(appLocalizations.submit), child: Text(appLocalizations.submit),
) )
], ],

View File

@@ -1,12 +1,11 @@
import 'dart:async'; import 'dart:async';
import 'package:fl_clash/clash/clash.dart'; import 'package:fl_clash/clash/core.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/models/models.dart';
import 'package:fl_clash/plugins/app.dart';
import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/widgets.dart'; import 'package:fl_clash/widgets/widgets.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class ConnectionsFragment extends StatefulWidget { class ConnectionsFragment extends StatefulWidget {
const ConnectionsFragment({super.key}); const ConnectionsFragment({super.key});
@@ -16,330 +15,124 @@ class ConnectionsFragment extends StatefulWidget {
} }
class _ConnectionsFragmentState extends State<ConnectionsFragment> { class _ConnectionsFragmentState extends State<ConnectionsFragment> {
final connectionsNotifier = final connectionsNotifier = ValueNotifier<List<Connection>>([]);
ValueNotifier<ConnectionsAndKeywords>(const ConnectionsAndKeywords()); Map<String, String?> idPackageNameMap = {};
final ScrollController _scrollController = ScrollController(
keepScrollOffset: false,
);
Timer? timer; Timer? timer;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
connectionsNotifier.value = connectionsNotifier.value _getConnections();
.copyWith(connections: clashCore.getConnections());
if (timer != null) { if (timer != null) {
timer?.cancel(); timer?.cancel();
timer = null; timer = null;
} }
timer = Timer.periodic( timer = Timer.periodic(const Duration(seconds: 3), (timer) {
const Duration(seconds: 1), if (mounted) {
(timer) { _getConnections();
connectionsNotifier.value = connectionsNotifier.value.copyWith( }
connections: clashCore.getConnections(), });
);
},
);
}); });
} }
_initActions() { _getConnections() {
WidgetsBinding.instance.addPostFrameCallback( connectionsNotifier.value = clashCore
(_) { .getConnections();
final commonScaffoldState =
context.findAncestorStateOfType<CommonScaffoldState>();
commonScaffoldState?.actions = [
IconButton(
onPressed: () {
showSearch(
context: context,
delegate: ConnectionsSearchDelegate(
state: connectionsNotifier.value,
),
);
},
icon: const Icon(Icons.search),
),
const SizedBox(
width: 8,
),
IconButton(
onPressed: () {
clashCore.closeConnections();
connectionsNotifier.value = connectionsNotifier.value.copyWith(
connections: clashCore.getConnections(),
);
},
icon: const Icon(Icons.delete_sweep_outlined),
),
];
},
);
}
_addKeyword(String keyword) {
final isContains = connectionsNotifier.value.keywords.contains(keyword);
if (isContains) return;
final keywords = List<String>.from(connectionsNotifier.value.keywords)
..add(keyword);
connectionsNotifier.value = connectionsNotifier.value.copyWith(
keywords: keywords,
);
}
_deleteKeyword(String keyword) {
final isContains = connectionsNotifier.value.keywords.contains(keyword);
if (!isContains) return;
final keywords = List<String>.from(connectionsNotifier.value.keywords)
..remove(keyword);
connectionsNotifier.value = connectionsNotifier.value.copyWith(
keywords: keywords,
);
}
_handleBlockConnection(String id) {
clashCore.closeConnection(id);
connectionsNotifier.value = connectionsNotifier.value
.copyWith(connections: clashCore.getConnections());
} }
@override @override
void dispose() { void dispose() {
super.dispose(); super.dispose();
timer?.cancel(); timer?.cancel();
connectionsNotifier.dispose();
_scrollController.dispose();
timer = null; timer = null;
} }
Future<ImageProvider?> _getPackageIconWithConnection(
Connection connection) async {
final uid = connection.metadata.uid;
// if(globalState.packageNameMap[uid] == null){
// globalState.packageNameMap[uid] = await app?.getPackageName(connection.metadata);
// }
final packageName = globalState.packageNameMap[uid];
if(packageName == null) return null;
return await app?.getPackageIcon(packageName);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Selector<AppState, bool?>( return ValueListenableBuilder<List<Connection>>(
selector: (_, appState) =>
appState.currentLabel == 'connections' ||
appState.viewMode == ViewMode.mobile &&
appState.currentLabel == "tools",
builder: (_, isCurrent, child) {
if (isCurrent == null || isCurrent) {
_initActions();
}
return child!;
},
child: ValueListenableBuilder<ConnectionsAndKeywords>(
valueListenable: connectionsNotifier,
builder: (_, state, __) {
var connections = state.filteredConnections;
if (connections.isEmpty) {
return NullStatus(
label: appLocalizations.nullConnectionsDesc,
);
}
connections = connections.reversed.toList();
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (state.keywords.isNotEmpty)
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 16,
),
child: Wrap(
runSpacing: 6,
spacing: 6,
children: [
for (final keyword in state.keywords)
CommonChip(
label: keyword,
type: ChipType.delete,
onPressed: () {
_deleteKeyword(keyword);
},
),
],
),
),
Expanded(
child: ListView.separated(
controller: _scrollController,
itemBuilder: (_, index) {
final connection = connections[index];
return ConnectionItem(
key: Key(connection.id),
connection: connection,
onClick: _addKeyword,
trailing: IconButton(
icon: const Icon(Icons.block),
onPressed: () {
_handleBlockConnection(connection.id);
},
),
);
},
separatorBuilder: (BuildContext context, int index) {
return const Divider(
height: 0,
);
},
itemCount: connections.length,
),
)
],
);
},
),
);
}
}
class ConnectionsSearchDelegate extends SearchDelegate {
ValueNotifier<ConnectionsAndKeywords> connectionsNotifier;
ConnectionsSearchDelegate({
required ConnectionsAndKeywords state,
}) : connectionsNotifier = ValueNotifier<ConnectionsAndKeywords>(state);
get state => connectionsNotifier.value;
List<Connection> get _results {
final lowerQuery = query.toLowerCase().trim();
return connectionsNotifier.value.filteredConnections.where((request) {
final lowerNetwork = request.metadata.network.toLowerCase();
final lowerHost = request.metadata.host.toLowerCase();
final lowerDestinationIP = request.metadata.destinationIP.toLowerCase();
final lowerProcess = request.metadata.process.toLowerCase();
final lowerChains = request.chains.join("").toLowerCase();
return lowerNetwork.contains(lowerQuery) ||
lowerHost.contains(lowerQuery) ||
lowerDestinationIP.contains(lowerQuery) ||
lowerProcess.contains(lowerQuery) ||
lowerChains.contains(lowerQuery);
}).toList();
}
_addKeyword(String keyword) {
final isContains = connectionsNotifier.value.keywords.contains(keyword);
if (isContains) return;
final keywords = List<String>.from(connectionsNotifier.value.keywords)
..add(keyword);
connectionsNotifier.value = connectionsNotifier.value.copyWith(
keywords: keywords,
);
}
_deleteKeyword(String keyword) {
final isContains = connectionsNotifier.value.keywords.contains(keyword);
if (!isContains) return;
final keywords = List<String>.from(connectionsNotifier.value.keywords)
..remove(keyword);
connectionsNotifier.value = connectionsNotifier.value.copyWith(
keywords: keywords,
);
}
_handleBlockConnection(String id) {
clashCore.closeConnection(id);
connectionsNotifier.value = connectionsNotifier.value.copyWith(
connections: clashCore.getConnections(),
);
}
@override
List<Widget>? buildActions(BuildContext context) {
return [
IconButton(
onPressed: () {
if (query.isEmpty) {
close(context, null);
return;
}
query = '';
},
icon: const Icon(Icons.clear),
),
const SizedBox(
width: 8,
)
];
}
@override
Widget? buildLeading(BuildContext context) {
return IconButton(
onPressed: () {
close(context, null);
},
icon: const Icon(Icons.arrow_back),
);
}
@override
Widget buildResults(BuildContext context) {
return buildSuggestions(context);
}
@override
void dispose() {
connectionsNotifier.dispose();
super.dispose();
}
@override
Widget buildSuggestions(BuildContext context) {
return ValueListenableBuilder(
valueListenable: connectionsNotifier, valueListenable: connectionsNotifier,
builder: (_, __, ___) { builder: (_, List<Connection> connections, __) {
return Column( if (connections.isEmpty) {
crossAxisAlignment: CrossAxisAlignment.start, return const NullStatus(
children: [ label: "未开启代理,或者没有连接数据",
if (state.keywords.isNotEmpty) );
Padding( }
padding: const EdgeInsets.symmetric( return ListView.separated(
horizontal: 16, physics: const AlwaysScrollableScrollPhysics(),
vertical: 16, itemBuilder: (_, index) {
), final connection = connections[index];
child: Wrap( return ListTile(
runSpacing: 6, titleAlignment: ListTileTitleAlignment.top,
spacing: 6, leading: Container(
children: [ margin: const EdgeInsets.only(top: 4),
for (final keyword in state.keywords) width: 48,
CommonChip( height: 48,
label: keyword, child: FutureBuilder<ImageProvider?>(
type: ChipType.delete, future: _getPackageIconWithConnection(connection),
onPressed: () { builder: (_, snapshot) {
_deleteKeyword(keyword); if (!snapshot.hasData && snapshot.data == null) {
}, return Container();
), } else {
], return Image(
image: snapshot.data!,
gaplessPlayback: true,
width: 48,
height: 48,
);
}
},
), ),
), ),
Expanded( contentPadding:
child: ListView.separated( const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
itemBuilder: (_, index) { title: Column(
final connection = _results[index]; crossAxisAlignment: CrossAxisAlignment.start,
return ConnectionItem( children: [
key: Key(connection.id), Text(connection.metadata.host.isNotEmpty
connection: connection, ? connection.metadata.host
onClick: _addKeyword, : connection.metadata.destinationIP),
trailing: IconButton( Padding(
icon: const Icon(Icons.block), padding: const EdgeInsets.only(
onPressed: () { top: 12,
_handleBlockConnection(connection.id);
},
), ),
); child: Wrap(
}, runSpacing: 8,
separatorBuilder: (BuildContext context, int index) { spacing: 8,
return const Divider( children: [
height: 0, for (final chain in connection.chains)
); CommonChip(
}, label: chain,
itemCount: _results.length, ),
],
),
),
],
), ),
) trailing: IconButton(
], icon: const Icon(Icons.block),
onPressed: () {},
),
);
},
separatorBuilder: (BuildContext context, int index) {
return const Divider(
height: 0,
);
},
itemCount: connections.length,
); );
}, },
); );

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