Separate android ui and vpn
This commit is contained in:
@@ -1,9 +1,14 @@
|
||||
package com.follow.clash
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
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
|
||||
import java.util.Date
|
||||
|
||||
enum class RunState {
|
||||
@@ -12,16 +17,41 @@ enum class RunState {
|
||||
STOP
|
||||
}
|
||||
|
||||
class GlobalState {
|
||||
companion object {
|
||||
val runState: MutableLiveData<RunState> = MutableLiveData<RunState>(RunState.STOP)
|
||||
var runTime: Date? = null
|
||||
var flutterEngine: FlutterEngine? = null
|
||||
fun getCurrentTilePlugin(): TilePlugin? =
|
||||
flutterEngine?.plugins?.get(TilePlugin::class.java) as TilePlugin?
|
||||
object GlobalState {
|
||||
val runState: MutableLiveData<RunState> = MutableLiveData<RunState>(RunState.STOP)
|
||||
var flutterEngine: FlutterEngine? = null
|
||||
private var serviceEngine: FlutterEngine? = null
|
||||
|
||||
fun getCurrentAppPlugin(): AppPlugin? =
|
||||
flutterEngine?.plugins?.get(AppPlugin::class.java) as AppPlugin?
|
||||
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
|
||||
val 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,
|
||||
listOf("${flutterEngine == null}")
|
||||
)
|
||||
GlobalState.serviceEngine = serviceEngine
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ import io.flutter.embedding.engine.FlutterEngine
|
||||
class MainActivity : FlutterActivity() {
|
||||
|
||||
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
||||
GlobalState.flutterEngine?.destroy()
|
||||
super.configureFlutterEngine(flutterEngine)
|
||||
flutterEngine.plugins.add(AppPlugin())
|
||||
flutterEngine.plugins.add(ProxyPlugin())
|
||||
|
||||
@@ -11,6 +11,7 @@ import android.os.Build
|
||||
import android.widget.Toast
|
||||
import androidx.core.content.ContextCompat.getSystemService
|
||||
import androidx.core.content.getSystemService
|
||||
import com.follow.clash.GlobalState
|
||||
import com.follow.clash.extensions.getBase64
|
||||
import com.follow.clash.extensions.getProtocol
|
||||
import com.follow.clash.models.Package
|
||||
@@ -21,6 +22,7 @@ import io.flutter.embedding.engine.plugins.activity.ActivityAware
|
||||
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
|
||||
import io.flutter.plugin.common.MethodCall
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
import io.flutter.plugin.common.MethodChannel.Result
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.cancel
|
||||
@@ -56,14 +58,16 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
|
||||
}
|
||||
|
||||
private fun tip(message: String?) {
|
||||
if (toast != null) {
|
||||
toast!!.cancel()
|
||||
if(GlobalState.flutterEngine == null){
|
||||
if (toast != null) {
|
||||
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: MethodChannel.Result) {
|
||||
override fun onMethodCall(call: MethodCall, result: Result) {
|
||||
when (call.method) {
|
||||
"moveTaskToBack" -> {
|
||||
activity?.moveTaskToBack(true)
|
||||
@@ -163,13 +167,17 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun updateExcludeFromRecents(value: Boolean?) {
|
||||
if (context == null) return
|
||||
val am = getSystemService(context!!, ActivityManager::class.java)
|
||||
val task = am?.appTasks?.firstOrNull { task ->
|
||||
task.taskInfo.baseIntent.component?.packageName == context!!.packageName
|
||||
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)
|
||||
@@ -220,7 +228,7 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
|
||||
}
|
||||
|
||||
override fun onDetachedFromActivityForConfigChanges() {
|
||||
activity = null;
|
||||
activity = null
|
||||
}
|
||||
|
||||
override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
|
||||
@@ -230,6 +238,6 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
|
||||
override fun onDetachedFromActivity() {
|
||||
channel.invokeMethod("exit", null)
|
||||
scope.cancel()
|
||||
activity = null;
|
||||
activity = null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.follow.clash.plugins
|
||||
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
@@ -9,8 +8,7 @@ import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.VpnService
|
||||
import android.os.Build.VERSION
|
||||
import android.os.Build.VERSION_CODES
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
@@ -25,38 +23,36 @@ import io.flutter.embedding.engine.plugins.activity.ActivityAware
|
||||
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
|
||||
import io.flutter.plugin.common.MethodCall
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
import java.util.Date
|
||||
|
||||
|
||||
class ProxyPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware {
|
||||
|
||||
private lateinit var flutterMethodChannel: MethodChannel
|
||||
|
||||
val VPN_PERMISSION_REQUEST_CODE = 1001
|
||||
val NOTIFICATION_PERMISSION_REQUEST_CODE = 1002
|
||||
|
||||
private lateinit var flutterMethodChannel: MethodChannel
|
||||
|
||||
private var activity: Activity? = null
|
||||
private var context: Context? = null
|
||||
private var flClashVpnService: FlClashVpnService? = null
|
||||
private var isBound = false
|
||||
private var port: Int? = null
|
||||
private var port: Int = 7890
|
||||
private var props: Props? = null
|
||||
private lateinit var title: String
|
||||
private lateinit var content: String
|
||||
var isBlockNotification: Boolean = false
|
||||
private var isBlockNotification: Boolean = false
|
||||
private var isStart: Boolean = false
|
||||
|
||||
private val connection = object : ServiceConnection {
|
||||
override fun onServiceConnected(className: ComponentName, service: IBinder) {
|
||||
val binder = service as FlClashVpnService.LocalBinder
|
||||
flClashVpnService = binder.getService()
|
||||
port?.let { startVpn(it) }
|
||||
isBound = true
|
||||
if (isStart) {
|
||||
startVpn()
|
||||
} else {
|
||||
flClashVpnService?.initServiceEngine()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(arg0: ComponentName) {
|
||||
override fun onServiceDisconnected(arg: ComponentName) {
|
||||
flClashVpnService = null
|
||||
isBound = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,21 +67,29 @@ class ProxyPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAwar
|
||||
}
|
||||
|
||||
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) = when (call.method) {
|
||||
"StartProxy" -> {
|
||||
port = call.argument<Int>("port")
|
||||
val args = call.argument<String>("args")
|
||||
props =
|
||||
if (args != null) Gson().fromJson(args, Props::class.java) else null
|
||||
handleStartVpn()
|
||||
"initService" -> {
|
||||
isStart = false
|
||||
initService()
|
||||
requestNotificationsPermission()
|
||||
result.success(true)
|
||||
}
|
||||
|
||||
"StopProxy" -> {
|
||||
"startProxy" -> {
|
||||
isStart = true
|
||||
port = call.argument<Int>("port")!!
|
||||
val args = call.argument<String>("args")
|
||||
props =
|
||||
if (args != null) Gson().fromJson(args, Props::class.java) else null
|
||||
startVpn()
|
||||
result.success(true)
|
||||
}
|
||||
|
||||
"stopProxy" -> {
|
||||
stopVpn()
|
||||
result.success(true)
|
||||
}
|
||||
|
||||
"SetProtect" -> {
|
||||
"setProtect" -> {
|
||||
val fd = call.argument<Int>("fd")
|
||||
if (fd != null) {
|
||||
flClashVpnService?.protect(fd)
|
||||
@@ -95,14 +99,10 @@ class ProxyPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAwar
|
||||
}
|
||||
}
|
||||
|
||||
"GetRunTimeStamp" -> {
|
||||
result.success(GlobalState.runTime?.time)
|
||||
}
|
||||
|
||||
"startForeground" -> {
|
||||
title = call.argument<String>("title") as String
|
||||
content = call.argument<String>("content") as String
|
||||
requestNotificationsPermission()
|
||||
val title = call.argument<String>("title") as String
|
||||
val content = call.argument<String>("content") as String
|
||||
startForeground(title, content)
|
||||
result.success(true)
|
||||
}
|
||||
|
||||
@@ -111,65 +111,41 @@ class ProxyPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAwar
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleStartVpn() {
|
||||
private fun initService() {
|
||||
val intent = VpnService.prepare(context)
|
||||
if (intent != null) {
|
||||
activity?.startActivityForResult(intent, VPN_PERMISSION_REQUEST_CODE)
|
||||
} else {
|
||||
bindService()
|
||||
if (flClashVpnService != null) {
|
||||
flClashVpnService!!.initServiceEngine()
|
||||
} else {
|
||||
bindService()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun startVpn(port: Int) {
|
||||
if (GlobalState.runState.value == RunState.START) return;
|
||||
flClashVpnService?.start(port, props)
|
||||
private fun startVpn() {
|
||||
if (flClashVpnService == null) {
|
||||
bindService()
|
||||
return
|
||||
}
|
||||
if (GlobalState.runState.value == RunState.START) return
|
||||
GlobalState.runState.value = RunState.START
|
||||
GlobalState.runTime = Date()
|
||||
startAfter()
|
||||
flutterMethodChannel.invokeMethod("started", flClashVpnService?.start(port, props))
|
||||
}
|
||||
|
||||
private fun stopVpn() {
|
||||
if (GlobalState.runState.value == RunState.STOP) return
|
||||
GlobalState.runState.value = RunState.STOP
|
||||
flClashVpnService?.stop()
|
||||
unbindService()
|
||||
GlobalState.runState.value = RunState.STOP;
|
||||
GlobalState.runTime = null;
|
||||
GlobalState.destroyServiceEngine()
|
||||
}
|
||||
|
||||
private fun startForeground() {
|
||||
private fun startForeground(title: String, content: String) {
|
||||
if (GlobalState.runState.value != RunState.START) return
|
||||
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 {
|
||||
if (isBlockNotification) return
|
||||
if (activity == null) return
|
||||
ActivityCompat.requestPermissions(
|
||||
activity!!,
|
||||
arrayOf(Manifest.permission.POST_NOTIFICATIONS),
|
||||
NOTIFICATION_PERMISSION_REQUEST_CODE
|
||||
)
|
||||
|
||||
}
|
||||
} else {
|
||||
startForeground()
|
||||
}
|
||||
}
|
||||
|
||||
private fun startAfter() {
|
||||
flutterMethodChannel.invokeMethod("startAfter", flClashVpnService?.fd)
|
||||
}
|
||||
|
||||
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
|
||||
activity = binding.activity
|
||||
binding.addActivityResultListener(::onActivityResult)
|
||||
@@ -184,7 +160,7 @@ class ProxyPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAwar
|
||||
stopVpn()
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return true
|
||||
}
|
||||
|
||||
private fun onRequestPermissionsResultListener(
|
||||
@@ -194,18 +170,32 @@ class ProxyPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAwar
|
||||
): Boolean {
|
||||
if (requestCode == NOTIFICATION_PERMISSION_REQUEST_CODE) {
|
||||
isBlockNotification = true
|
||||
if (grantResults.isNotEmpty()) {
|
||||
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
startForeground()
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return false
|
||||
}
|
||||
|
||||
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() {
|
||||
activity = null;
|
||||
activity = null
|
||||
}
|
||||
|
||||
override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
|
||||
@@ -213,7 +203,6 @@ class ProxyPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAwar
|
||||
}
|
||||
|
||||
override fun onDetachedFromActivity() {
|
||||
stopVpn()
|
||||
activity = null
|
||||
}
|
||||
|
||||
@@ -221,11 +210,4 @@ class ProxyPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAwar
|
||||
val intent = Intent(context, FlClashVpnService::class.java)
|
||||
context?.bindService(intent, connection, Context.BIND_AUTO_CREATE)
|
||||
}
|
||||
|
||||
private fun unbindService() {
|
||||
if (isBound) {
|
||||
context?.unbindService(connection)
|
||||
isBound = false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
|
||||
package com.follow.clash.plugins
|
||||
|
||||
import io.flutter.embedding.engine.plugins.FlutterPlugin
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package com.follow.clash.services
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.PendingIntent
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import android.service.quicksettings.Tile
|
||||
import android.service.quicksettings.TileService
|
||||
import androidx.annotation.RequiresApi
|
||||
@@ -11,14 +11,9 @@ import androidx.lifecycle.Observer
|
||||
import com.follow.clash.GlobalState
|
||||
import com.follow.clash.RunState
|
||||
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)
|
||||
class FlClashTileService : TileService() {
|
||||
|
||||
private val observer = Observer<RunState> { runState ->
|
||||
@@ -62,42 +57,25 @@ class FlClashTileService : TileService() {
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||
startActivityAndCollapse(pendingIntent)
|
||||
}else{
|
||||
} else {
|
||||
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() {
|
||||
super.onClick()
|
||||
activityTransfer()
|
||||
val currentTilePlugin = GlobalState.getCurrentTilePlugin()
|
||||
if (GlobalState.runState.value == RunState.STOP) {
|
||||
GlobalState.runState.value = RunState.PENDING
|
||||
if(currentTilePlugin == null){
|
||||
initFlutterEngine()
|
||||
}else{
|
||||
currentTilePlugin.handleStart()
|
||||
val titlePlugin = GlobalState.getCurrentTitlePlugin()
|
||||
if (titlePlugin != null) {
|
||||
titlePlugin.handleStart()
|
||||
} else {
|
||||
GlobalState.initServiceEngine(this)
|
||||
}
|
||||
} else if(GlobalState.runState.value == RunState.START){
|
||||
} else if (GlobalState.runState.value == RunState.START) {
|
||||
GlobalState.runState.value = RunState.PENDING
|
||||
currentTilePlugin?.handleStop()
|
||||
GlobalState.getCurrentTitlePlugin()?.handleStop()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -20,11 +20,8 @@ import com.follow.clash.models.Props
|
||||
|
||||
|
||||
class FlClashVpnService : VpnService() {
|
||||
|
||||
|
||||
private val CHANNEL = "FlClash"
|
||||
|
||||
var fd: Int? = null
|
||||
private val notificationId: Int = 1
|
||||
|
||||
private val passList = listOf(
|
||||
@@ -47,8 +44,8 @@ class FlClashVpnService : VpnService() {
|
||||
"192.168.*"
|
||||
)
|
||||
|
||||
fun start(port: Int, props: Props?) {
|
||||
fd = with(Builder()) {
|
||||
fun start(port: Int, props: Props?): Int? {
|
||||
return with(Builder()) {
|
||||
addAddress("172.16.0.1", 30)
|
||||
setMtu(9000)
|
||||
addRoute("0.0.0.0", 0)
|
||||
@@ -94,7 +91,6 @@ class FlClashVpnService : VpnService() {
|
||||
stopForeground()
|
||||
}
|
||||
|
||||
|
||||
private val notificationBuilder: NotificationCompat.Builder by lazy {
|
||||
val intent = Intent(this, MainActivity::class.java)
|
||||
|
||||
@@ -130,6 +126,10 @@ class FlClashVpnService : VpnService() {
|
||||
}
|
||||
}
|
||||
|
||||
fun initServiceEngine() {
|
||||
GlobalState.initServiceEngine(this)
|
||||
}
|
||||
|
||||
override fun onTrimMemory(level: Int) {
|
||||
super.onTrimMemory(level)
|
||||
GlobalState.getCurrentAppPlugin()?.requestGc()
|
||||
@@ -171,7 +171,6 @@ class FlClashVpnService : VpnService() {
|
||||
}
|
||||
|
||||
override fun onUnbind(intent: Intent?): Boolean {
|
||||
GlobalState.getCurrentTilePlugin()?.handleStop();
|
||||
return super.onUnbind(intent)
|
||||
}
|
||||
|
||||
|
||||
@@ -84,26 +84,11 @@ type TestDelayParams struct {
|
||||
Timeout int64 `json:"timeout"`
|
||||
}
|
||||
|
||||
type Delay struct {
|
||||
Name string `json:"name"`
|
||||
Value int32 `json:"value"`
|
||||
}
|
||||
|
||||
type Process struct {
|
||||
Id int64 `json:"id"`
|
||||
Metadata *constant.Metadata `json:"metadata"`
|
||||
}
|
||||
|
||||
type ProcessMapItem struct {
|
||||
Id int64 `json:"id"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type Now struct {
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type ExternalProvider struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
|
||||
@@ -25,7 +25,7 @@ func InitDartApi(api unsafe.Pointer) {
|
||||
}
|
||||
}
|
||||
|
||||
func SendToPort(port int64, msg string) {
|
||||
func SendToPort(port int64, msg string) bool {
|
||||
var obj C.Dart_CObject
|
||||
obj._type = C.Dart_CObject_kString
|
||||
msgString := C.CString(msg)
|
||||
@@ -34,6 +34,7 @@ func SendToPort(port int64, msg string) {
|
||||
*(**C.char)(ptr) = msgString
|
||||
isSuccess := C.GoDart_PostCObject(C.Dart_Port_DL(port), &obj)
|
||||
if !isSuccess {
|
||||
fmt.Println("ERROR: post to port ", port, " failed", msg)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
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"
|
||||
Request MessageType = "request"
|
||||
Run MessageType = "run"
|
||||
Loaded MessageType = "loaded"
|
||||
)
|
||||
|
||||
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) {
|
||||
if Port == nil {
|
||||
return
|
||||
}
|
||||
s, err := message.Json()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
SendToPort(*Port, s)
|
||||
}
|
||||
32
core/hub.go
32
core/hub.go
@@ -36,6 +36,8 @@ var configParams = ConfigExtendedParams{}
|
||||
|
||||
var isInit = false
|
||||
|
||||
var currentProfileName = ""
|
||||
|
||||
//export initClash
|
||||
func initClash(homeDirStr *C.char) bool {
|
||||
if !isInit {
|
||||
@@ -74,6 +76,16 @@ func forceGc() {
|
||||
}()
|
||||
}
|
||||
|
||||
//export setCurrentProfileName
|
||||
func setCurrentProfileName(s *C.char) {
|
||||
currentProfileName = C.GoString(s)
|
||||
}
|
||||
|
||||
//export getCurrentProfileName
|
||||
func getCurrentProfileName() *C.char {
|
||||
return C.CString(currentProfileName)
|
||||
}
|
||||
|
||||
//export validateConfig
|
||||
func validateConfig(s *C.char, port C.longlong) {
|
||||
i := int64(port)
|
||||
@@ -415,10 +427,14 @@ func updateExternalProvider(providerName *C.char, providerType *C.char, port C.l
|
||||
}
|
||||
|
||||
//export initNativeApiBridge
|
||||
func initNativeApiBridge(api unsafe.Pointer, port C.longlong) {
|
||||
func initNativeApiBridge(api unsafe.Pointer) {
|
||||
bridge.InitDartApi(api)
|
||||
}
|
||||
|
||||
//export initMessage
|
||||
func initMessage(port C.longlong) {
|
||||
i := int64(port)
|
||||
bridge.Port = &i
|
||||
Port = i
|
||||
}
|
||||
|
||||
//export freeCString
|
||||
@@ -436,20 +452,20 @@ func init() {
|
||||
} else {
|
||||
delayData.Value = int32(delay)
|
||||
}
|
||||
bridge.SendMessage(bridge.Message{
|
||||
Type: bridge.Delay,
|
||||
SendMessage(Message{
|
||||
Type: DelayMessage,
|
||||
Data: delayData,
|
||||
})
|
||||
}
|
||||
statistic.DefaultRequestNotify = func(c statistic.Tracker) {
|
||||
bridge.SendMessage(bridge.Message{
|
||||
Type: bridge.Request,
|
||||
SendMessage(Message{
|
||||
Type: RequestMessage,
|
||||
Data: c,
|
||||
})
|
||||
}
|
||||
executor.DefaultProxyProviderLoadedHook = func(providerName string) {
|
||||
bridge.SendMessage(bridge.Message{
|
||||
Type: bridge.Loaded,
|
||||
SendMessage(Message{
|
||||
Type: LoadedMessage,
|
||||
Data: providerName,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package main
|
||||
|
||||
import "C"
|
||||
import (
|
||||
bridge "core/dart-bridge"
|
||||
"github.com/metacubex/mihomo/common/observable"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
)
|
||||
@@ -21,11 +20,11 @@ func startLog() {
|
||||
if logData.LogLevel < log.Level() {
|
||||
continue
|
||||
}
|
||||
message := &bridge.Message{
|
||||
Type: bridge.Log,
|
||||
message := &Message{
|
||||
Type: LogMessage,
|
||||
Data: logData,
|
||||
}
|
||||
bridge.SendMessage(*message)
|
||||
SendMessage(*message)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
77
core/message.go
Normal file
77
core/message.go
Normal file
@@ -0,0 +1,77 @@
|
||||
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
|
||||
}
|
||||
@@ -4,7 +4,6 @@ package main
|
||||
|
||||
import "C"
|
||||
import (
|
||||
bridge "core/dart-bridge"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"github.com/metacubex/mihomo/component/process"
|
||||
@@ -43,8 +42,8 @@ func init() {
|
||||
|
||||
timeout := time.After(200 * time.Millisecond)
|
||||
|
||||
bridge.SendMessage(bridge.Message{
|
||||
Type: bridge.Process,
|
||||
SendMessage(Message{
|
||||
Type: ProcessMessage,
|
||||
Data: Process{
|
||||
Id: id,
|
||||
Metadata: metadata,
|
||||
|
||||
41
core/tun.go
41
core/tun.go
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
"golang.org/x/sync/semaphore"
|
||||
"strconv"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
@@ -18,6 +19,7 @@ import (
|
||||
|
||||
var tunLock sync.Mutex
|
||||
var tun *t.Tun
|
||||
var runTime *time.Time
|
||||
|
||||
type FdMap struct {
|
||||
m sync.Map
|
||||
@@ -35,7 +37,9 @@ func (cm *FdMap) Load(key int64) bool {
|
||||
var fdMap FdMap
|
||||
|
||||
//export startTUN
|
||||
func startTUN(fd C.int) {
|
||||
func startTUN(fd C.int, port C.longlong) {
|
||||
i := int64(port)
|
||||
ServicePort = i
|
||||
go func() {
|
||||
tunLock.Lock()
|
||||
defer tunLock.Unlock()
|
||||
@@ -44,6 +48,7 @@ func startTUN(fd C.int) {
|
||||
tun.Close()
|
||||
tun = nil
|
||||
}
|
||||
|
||||
f := int(fd)
|
||||
gateway := "172.16.0.1/30"
|
||||
portal := "172.16.0.2"
|
||||
@@ -61,15 +66,34 @@ func startTUN(fd C.int) {
|
||||
tempTun.Closer = closer
|
||||
|
||||
tun = tempTun
|
||||
|
||||
now := time.Now()
|
||||
|
||||
runTime = &now
|
||||
|
||||
SendMessage(Message{
|
||||
Type: StartedMessage,
|
||||
Data: strconv.FormatInt(runTime.UnixMilli(), 10),
|
||||
})
|
||||
}()
|
||||
}
|
||||
|
||||
//export getRunTime
|
||||
func getRunTime() *C.char {
|
||||
if runTime == nil {
|
||||
return C.CString("")
|
||||
}
|
||||
return C.CString(strconv.FormatInt(runTime.UnixMilli(), 10))
|
||||
}
|
||||
|
||||
//export stopTun
|
||||
func stopTun() {
|
||||
go func() {
|
||||
tunLock.Lock()
|
||||
defer tunLock.Unlock()
|
||||
|
||||
runTime = nil
|
||||
|
||||
if tun != nil {
|
||||
tun.Close()
|
||||
tun = nil
|
||||
@@ -87,6 +111,18 @@ func setFdMap(fd C.long) {
|
||||
}()
|
||||
}
|
||||
|
||||
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() {
|
||||
@@ -102,7 +138,8 @@ func init() {
|
||||
fdInt := int64(fd)
|
||||
timeout := time.After(100 * time.Millisecond)
|
||||
id := atomic.AddInt64(&fdCounter, 1)
|
||||
tun.MarkSocket(t.Fd{
|
||||
|
||||
markSocket(Fd{
|
||||
Id: id,
|
||||
Value: fdInt,
|
||||
})
|
||||
|
||||
@@ -5,7 +5,6 @@ package tun
|
||||
import "C"
|
||||
import (
|
||||
"context"
|
||||
bridge "core/dart-bridge"
|
||||
"encoding/binary"
|
||||
"github.com/Kr328/tun2socket"
|
||||
"github.com/Kr328/tun2socket/nat"
|
||||
@@ -185,24 +184,3 @@ func Start(fd int, gateway, portal, dns string) (io.Closer, error) {
|
||||
|
||||
return stack, nil
|
||||
}
|
||||
|
||||
type Fd struct {
|
||||
Id int64 `json:"id"`
|
||||
Value int64 `json:"value"`
|
||||
}
|
||||
|
||||
func (t *Tun) MarkSocket(fd Fd) {
|
||||
_ = t.Limit.Acquire(context.Background(), 1)
|
||||
defer t.Limit.Release(1)
|
||||
|
||||
if t.Closed {
|
||||
return
|
||||
}
|
||||
|
||||
message := &bridge.Message{
|
||||
Type: bridge.Tun,
|
||||
Data: fd,
|
||||
}
|
||||
|
||||
bridge.SendMessage(*message)
|
||||
}
|
||||
|
||||
@@ -35,7 +35,6 @@ class ClashCore {
|
||||
clashFFI = ClashFFI(lib);
|
||||
clashFFI.initNativeApiBridge(
|
||||
NativeApi.initializeApiDLData,
|
||||
receiver.sendPort.nativePort,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -95,6 +94,28 @@ class ClashCore {
|
||||
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() {
|
||||
final proxiesRaw = clashFFI.getProxies();
|
||||
final proxiesRawString = proxiesRaw.cast<Utf8>().toDartString();
|
||||
@@ -242,8 +263,9 @@ class ClashCore {
|
||||
clashFFI.stopLog();
|
||||
}
|
||||
|
||||
startTun(int fd) {
|
||||
clashFFI.startTUN(fd);
|
||||
startTun(int fd, int port) {
|
||||
if (!Platform.isAndroid) return;
|
||||
clashFFI.startTUN(fd, port);
|
||||
}
|
||||
|
||||
requestGc() {
|
||||
@@ -265,11 +287,13 @@ class ClashCore {
|
||||
clashFFI.setFdMap(fd);
|
||||
}
|
||||
|
||||
// DateTime? getRunTime() {
|
||||
// final runTimeString = clashFFI.getRunTime().cast<Utf8>().toDartString();
|
||||
// if (runTimeString.isEmpty) return null;
|
||||
// return DateTime.fromMillisecondsSinceEpoch(int.parse(runTimeString));
|
||||
// }
|
||||
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() {
|
||||
final connectionsDataRaw = clashFFI.getConnections();
|
||||
|
||||
@@ -5190,6 +5190,30 @@ class ClashFFI {
|
||||
_lookup<ffi.NativeFunction<ffi.Void Function()>>('forceGc');
|
||||
late final _forceGc = _forceGcPtr.asFunction<void Function()>();
|
||||
|
||||
void setCurrentProfileName(
|
||||
ffi.Pointer<ffi.Char> s,
|
||||
) {
|
||||
return _setCurrentProfileName(
|
||||
s,
|
||||
);
|
||||
}
|
||||
|
||||
late final _setCurrentProfileNamePtr =
|
||||
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Char>)>>(
|
||||
'setCurrentProfileName');
|
||||
late final _setCurrentProfileName = _setCurrentProfileNamePtr
|
||||
.asFunction<void Function(ffi.Pointer<ffi.Char>)>();
|
||||
|
||||
ffi.Pointer<ffi.Char> getCurrentProfileName() {
|
||||
return _getCurrentProfileName();
|
||||
}
|
||||
|
||||
late final _getCurrentProfileNamePtr =
|
||||
_lookup<ffi.NativeFunction<ffi.Pointer<ffi.Char> Function()>>(
|
||||
'getCurrentProfileName');
|
||||
late final _getCurrentProfileName =
|
||||
_getCurrentProfileNamePtr.asFunction<ffi.Pointer<ffi.Char> Function()>();
|
||||
|
||||
void validateConfig(
|
||||
ffi.Pointer<ffi.Char> s,
|
||||
int port,
|
||||
@@ -5406,20 +5430,30 @@ class ClashFFI {
|
||||
|
||||
void initNativeApiBridge(
|
||||
ffi.Pointer<ffi.Void> api,
|
||||
int port,
|
||||
) {
|
||||
return _initNativeApiBridge(
|
||||
api,
|
||||
);
|
||||
}
|
||||
|
||||
late final _initNativeApiBridgePtr =
|
||||
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Void>)>>(
|
||||
'initNativeApiBridge');
|
||||
late final _initNativeApiBridge = _initNativeApiBridgePtr
|
||||
.asFunction<void Function(ffi.Pointer<ffi.Void>)>();
|
||||
|
||||
void initMessage(
|
||||
int port,
|
||||
) {
|
||||
return _initMessage(
|
||||
port,
|
||||
);
|
||||
}
|
||||
|
||||
late final _initNativeApiBridgePtr = _lookup<
|
||||
ffi.NativeFunction<
|
||||
ffi.Void Function(
|
||||
ffi.Pointer<ffi.Void>, ffi.LongLong)>>('initNativeApiBridge');
|
||||
late final _initNativeApiBridge = _initNativeApiBridgePtr
|
||||
.asFunction<void Function(ffi.Pointer<ffi.Void>, int)>();
|
||||
late final _initMessagePtr =
|
||||
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.LongLong)>>(
|
||||
'initMessage');
|
||||
late final _initMessage = _initMessagePtr.asFunction<void Function(int)>();
|
||||
|
||||
void freeCString(
|
||||
ffi.Pointer<ffi.Char> s,
|
||||
@@ -5467,15 +5501,28 @@ class ClashFFI {
|
||||
|
||||
void startTUN(
|
||||
int fd,
|
||||
int port,
|
||||
) {
|
||||
return _startTUN(
|
||||
fd,
|
||||
port,
|
||||
);
|
||||
}
|
||||
|
||||
late final _startTUNPtr =
|
||||
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.Int)>>('startTUN');
|
||||
late final _startTUN = _startTUNPtr.asFunction<void Function(int)>();
|
||||
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.Int, ffi.LongLong)>>(
|
||||
'startTUN');
|
||||
late final _startTUN = _startTUNPtr.asFunction<void Function(int, int)>();
|
||||
|
||||
ffi.Pointer<ffi.Char> getRunTime() {
|
||||
return _getRunTime();
|
||||
}
|
||||
|
||||
late final _getRunTimePtr =
|
||||
_lookup<ffi.NativeFunction<ffi.Pointer<ffi.Char> Function()>>(
|
||||
'getRunTime');
|
||||
late final _getRunTime =
|
||||
_getRunTimePtr.asFunction<ffi.Pointer<ffi.Char> Function()>();
|
||||
|
||||
void stopTun() {
|
||||
return _stopTun();
|
||||
|
||||
@@ -7,24 +7,6 @@ import 'package:flutter/foundation.dart';
|
||||
|
||||
import 'core.dart';
|
||||
|
||||
abstract mixin class ClashMessageListener {
|
||||
void onLog(Log log) {}
|
||||
|
||||
void onTun(Fd fd) {}
|
||||
|
||||
void onDelay(Delay delay) {}
|
||||
|
||||
void onProcess(Process process) {}
|
||||
|
||||
void onRequest(Connection connection) {}
|
||||
|
||||
void onNow(Now now) {}
|
||||
|
||||
void onRun(String runTime) {}
|
||||
|
||||
void onLoaded(String groupName) {}
|
||||
}
|
||||
|
||||
class ClashMessage {
|
||||
StreamSubscription? subscription;
|
||||
|
||||
@@ -34,31 +16,22 @@ class ClashMessage {
|
||||
subscription = null;
|
||||
}
|
||||
subscription = ClashCore.receiver.listen((message) {
|
||||
final m = Message.fromJson(json.decode(message));
|
||||
for (final ClashMessageListener listener in _listeners) {
|
||||
final m = AppMessage.fromJson(json.decode(message));
|
||||
for (final AppMessageListener listener in _listeners) {
|
||||
switch (m.type) {
|
||||
case MessageType.log:
|
||||
case AppMessageType.log:
|
||||
listener.onLog(Log.fromJson(m.data));
|
||||
break;
|
||||
case MessageType.tun:
|
||||
listener.onTun(Fd.fromJson(m.data));
|
||||
break;
|
||||
case MessageType.delay:
|
||||
case AppMessageType.delay:
|
||||
listener.onDelay(Delay.fromJson(m.data));
|
||||
break;
|
||||
case MessageType.process:
|
||||
listener.onProcess(Process.fromJson(m.data));
|
||||
break;
|
||||
case MessageType.now:
|
||||
listener.onNow(Now.fromJson(m.data));
|
||||
break;
|
||||
case MessageType.request:
|
||||
case AppMessageType.request:
|
||||
listener.onRequest(Connection.fromJson(m.data));
|
||||
break;
|
||||
case MessageType.run:
|
||||
listener.onRun(m.data);
|
||||
case AppMessageType.started:
|
||||
listener.onStarted(m.data);
|
||||
break;
|
||||
case MessageType.loaded:
|
||||
case AppMessageType.loaded:
|
||||
listener.onLoaded(m.data);
|
||||
break;
|
||||
}
|
||||
@@ -68,18 +41,18 @@ class ClashMessage {
|
||||
|
||||
static final ClashMessage instance = ClashMessage._();
|
||||
|
||||
final ObserverList<ClashMessageListener> _listeners =
|
||||
ObserverList<ClashMessageListener>();
|
||||
final ObserverList<AppMessageListener> _listeners =
|
||||
ObserverList<AppMessageListener>();
|
||||
|
||||
bool get hasListeners {
|
||||
return _listeners.isNotEmpty;
|
||||
}
|
||||
|
||||
void addListener(ClashMessageListener listener) {
|
||||
void addListener(AppMessageListener listener) {
|
||||
_listeners.add(listener);
|
||||
}
|
||||
|
||||
void removeListener(ClashMessageListener listener) {
|
||||
void removeListener(AppMessageListener listener) {
|
||||
_listeners.remove(listener);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:fl_clash/clash/clash.dart';
|
||||
import 'package:fl_clash/plugins/app.dart';
|
||||
|
||||
class Android {
|
||||
init() async {
|
||||
app?.onExit = () {
|
||||
clashCore.shutdown();
|
||||
exit(0);
|
||||
};
|
||||
app?.onExit = () {};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,20 +28,6 @@ class ProxyManager {
|
||||
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() {
|
||||
_instance ??= ProxyManager._internal();
|
||||
return _instance!;
|
||||
|
||||
@@ -26,7 +26,7 @@ class AppController {
|
||||
updateClashConfigDebounce = debounce<Function()>(() async {
|
||||
await updateClashConfig();
|
||||
});
|
||||
addCheckIpNumDebounce = debounce((){
|
||||
addCheckIpNumDebounce = debounce(() {
|
||||
appState.checkIpNum++;
|
||||
});
|
||||
measure = Measure.of(context);
|
||||
@@ -70,7 +70,6 @@ class AppController {
|
||||
|
||||
updateTraffic() {
|
||||
globalState.updateTraffic(
|
||||
config: config,
|
||||
appState: appState,
|
||||
);
|
||||
}
|
||||
@@ -121,6 +120,14 @@ class AppController {
|
||||
});
|
||||
}
|
||||
|
||||
Future rawApplyProfile() async {
|
||||
await globalState.applyProfile(
|
||||
appState: appState,
|
||||
config: config,
|
||||
clashConfig: clashConfig,
|
||||
);
|
||||
}
|
||||
|
||||
changeProfile(String? value) async {
|
||||
if (value == config.currentProfileId) return;
|
||||
config.currentProfileId = value;
|
||||
|
||||
@@ -56,14 +56,18 @@ enum ProfileType { file, url }
|
||||
|
||||
enum ResultType { success, error }
|
||||
|
||||
enum MessageType {
|
||||
enum AppMessageType {
|
||||
log,
|
||||
tun,
|
||||
delay,
|
||||
process,
|
||||
now,
|
||||
request,
|
||||
run,
|
||||
started,
|
||||
loaded,
|
||||
}
|
||||
|
||||
enum ServiceMessageType {
|
||||
protect,
|
||||
process,
|
||||
started,
|
||||
loaded,
|
||||
}
|
||||
|
||||
|
||||
116
lib/main.dart
116
lib/main.dart
@@ -1,8 +1,8 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:fl_clash/clash/clash.dart';
|
||||
import 'package:fl_clash/plugins/app.dart';
|
||||
import 'package:fl_clash/plugins/proxy.dart';
|
||||
import 'package:fl_clash/plugins/tile.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -16,6 +16,7 @@ Future<void> main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await android?.init();
|
||||
await window?.init();
|
||||
clashCore.initMessage();
|
||||
globalState.packageInfo = await PackageInfo.fromPlatform();
|
||||
final config = await preferences.getConfig() ?? Config();
|
||||
final clashConfig = await preferences.getClashConfig() ?? ClashConfig();
|
||||
@@ -44,6 +45,7 @@ Future<void> main() async {
|
||||
@pragma('vm:entry-point')
|
||||
Future<void> vpnService() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
globalState.isVpnService = true;
|
||||
final config = await preferences.getConfig() ?? Config();
|
||||
final clashConfig = await preferences.getClashConfig() ?? ClashConfig();
|
||||
final appState = AppState(
|
||||
@@ -51,12 +53,34 @@ Future<void> vpnService() async {
|
||||
isCompatible: config.isCompatible,
|
||||
selectedMap: config.currentSelectedMap,
|
||||
);
|
||||
clashMessage.addListener(
|
||||
ClashMessageListenerWithVpn(
|
||||
onTun: (Fd fd) async {
|
||||
await proxyManager.setProtect(fd.value);
|
||||
await globalState.init(
|
||||
appState: appState,
|
||||
config: config,
|
||||
clashConfig: clashConfig,
|
||||
);
|
||||
|
||||
proxy?.setServiceMessageHandler(
|
||||
ServiceMessageHandler(
|
||||
onProtect: (Fd fd) async {
|
||||
await proxy?.setProtect(fd.value);
|
||||
clashCore.setFdMap(fd.id);
|
||||
},
|
||||
onProcess: (Process process) async {
|
||||
var packageName = await app?.resolverProcess(process);
|
||||
clashCore.setProcessMap(
|
||||
ProcessMapItem(
|
||||
id: process.id,
|
||||
value: packageName ?? "",
|
||||
),
|
||||
);
|
||||
},
|
||||
onStarted: (String runTime) {
|
||||
globalState.applyProfile(
|
||||
appState: appState,
|
||||
config: config,
|
||||
clashConfig: clashConfig,
|
||||
);
|
||||
},
|
||||
onLoaded: (String groupName) {
|
||||
final currentSelectedMap = config.currentSelectedMap;
|
||||
final proxyName = currentSelectedMap[groupName];
|
||||
@@ -70,70 +94,68 @@ Future<void> vpnService() async {
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
await globalState.init(
|
||||
appState: appState,
|
||||
config: config,
|
||||
clashConfig: clashConfig,
|
||||
);
|
||||
|
||||
globalState.applyProfile(
|
||||
appState: appState,
|
||||
config: config,
|
||||
clashConfig: clashConfig,
|
||||
);
|
||||
|
||||
final appLocalizations = await AppLocalizations.load(
|
||||
other.getLocaleForString(config.locale) ??
|
||||
WidgetsBinding.instance.platformDispatcher.locale,
|
||||
);
|
||||
|
||||
handleStart() async {
|
||||
await app?.tip(appLocalizations.startVpn);
|
||||
await globalState.startSystemProxy(
|
||||
appState: appState,
|
||||
config: config,
|
||||
clashConfig: clashConfig,
|
||||
);
|
||||
globalState.updateTraffic(config: config);
|
||||
globalState.updateFunctionLists = [
|
||||
() {
|
||||
globalState.updateTraffic(config: config);
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
handleStart();
|
||||
await app?.tip(appLocalizations.startVpn);
|
||||
await globalState.startSystemProxy(
|
||||
appState: appState,
|
||||
config: config,
|
||||
clashConfig: clashConfig,
|
||||
);
|
||||
|
||||
tile?.addListener(
|
||||
TileListenerWithVpn(
|
||||
onStop: () async {
|
||||
await app?.tip(appLocalizations.stopVpn);
|
||||
await globalState.stopSystemProxy();
|
||||
clashCore.shutdown();
|
||||
exit(0);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
globalState.updateTraffic();
|
||||
globalState.updateFunctionLists = [
|
||||
() {
|
||||
globalState.updateTraffic();
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
class ClashMessageListenerWithVpn with ClashMessageListener {
|
||||
final Function(Fd fd) _onTun;
|
||||
final Function(String) _onLoaded;
|
||||
@immutable
|
||||
class ServiceMessageHandler with ServiceMessageListener {
|
||||
final Function(Fd fd) _onProtect;
|
||||
final Function(Process process) _onProcess;
|
||||
final Function(String runTime) _onStarted;
|
||||
final Function(String groupName) _onLoaded;
|
||||
|
||||
ClashMessageListenerWithVpn({
|
||||
required Function(Fd fd) onTun,
|
||||
required Function(String) onLoaded,
|
||||
}) : _onTun = onTun,
|
||||
const ServiceMessageHandler({
|
||||
required Function(Fd fd) onProtect,
|
||||
required Function(Process process) onProcess,
|
||||
required Function(String runTime) onStarted,
|
||||
required Function(String groupName) onLoaded,
|
||||
}) : _onProtect = onProtect,
|
||||
_onProcess = onProcess,
|
||||
_onStarted = onStarted,
|
||||
_onLoaded = onLoaded;
|
||||
|
||||
@override
|
||||
void onTun(Fd fd) {
|
||||
_onTun(fd);
|
||||
onProtect(Fd fd) {
|
||||
_onProtect(fd);
|
||||
}
|
||||
|
||||
@override
|
||||
void onLoaded(String groupName) {
|
||||
onProcess(Process process) {
|
||||
_onProcess(process);
|
||||
}
|
||||
|
||||
@override
|
||||
onStarted(String runTime) {
|
||||
_onStarted(runTime);
|
||||
}
|
||||
|
||||
@override
|
||||
onLoaded(String groupName) {
|
||||
_onLoaded(groupName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,14 +47,25 @@ class ChangeProxyParams with _$ChangeProxyParams {
|
||||
}
|
||||
|
||||
@freezed
|
||||
class Message with _$Message {
|
||||
const factory Message({
|
||||
required MessageType type,
|
||||
class AppMessage with _$AppMessage {
|
||||
const factory AppMessage({
|
||||
required AppMessageType type,
|
||||
dynamic data,
|
||||
}) = _Message;
|
||||
}) = _AppMessage;
|
||||
|
||||
factory Message.fromJson(Map<String, Object?> json) =>
|
||||
_$MessageFromJson(json);
|
||||
factory AppMessage.fromJson(Map<String, Object?> json) =>
|
||||
_$AppMessageFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class ServiceMessage with _$ServiceMessage {
|
||||
const factory ServiceMessage({
|
||||
required ServiceMessageType type,
|
||||
dynamic data,
|
||||
}) = _ServiceMessage;
|
||||
|
||||
factory ServiceMessage.fromJson(Map<String, Object?> json) =>
|
||||
_$ServiceMessageFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
@@ -121,3 +132,27 @@ class ExternalProvider with _$ExternalProvider {
|
||||
factory ExternalProvider.fromJson(Map<String, Object?> json) =>
|
||||
_$ExternalProviderFromJson(json);
|
||||
}
|
||||
|
||||
abstract mixin class AppMessageListener {
|
||||
void onLog(Log log) {}
|
||||
|
||||
void onDelay(Delay delay) {}
|
||||
|
||||
void onRequest(Connection connection) {}
|
||||
|
||||
void onStarted(String runTime) {}
|
||||
|
||||
void onLoaded(String groupName) {}
|
||||
}
|
||||
|
||||
abstract mixin class ServiceMessageListener {
|
||||
onProtect(Fd fd) {}
|
||||
|
||||
onProcess(Process process) {}
|
||||
|
||||
onStarted(String runTime) {}
|
||||
|
||||
onLoaded(String groupName) {}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -610,32 +610,34 @@ abstract class _ChangeProxyParams implements ChangeProxyParams {
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
Message _$MessageFromJson(Map<String, dynamic> json) {
|
||||
return _Message.fromJson(json);
|
||||
AppMessage _$AppMessageFromJson(Map<String, dynamic> json) {
|
||||
return _AppMessage.fromJson(json);
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$Message {
|
||||
MessageType get type => throw _privateConstructorUsedError;
|
||||
mixin _$AppMessage {
|
||||
AppMessageType get type => throw _privateConstructorUsedError;
|
||||
dynamic get data => throw _privateConstructorUsedError;
|
||||
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@JsonKey(ignore: true)
|
||||
$MessageCopyWith<Message> get copyWith => throw _privateConstructorUsedError;
|
||||
$AppMessageCopyWith<AppMessage> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $MessageCopyWith<$Res> {
|
||||
factory $MessageCopyWith(Message value, $Res Function(Message) then) =
|
||||
_$MessageCopyWithImpl<$Res, Message>;
|
||||
abstract class $AppMessageCopyWith<$Res> {
|
||||
factory $AppMessageCopyWith(
|
||||
AppMessage value, $Res Function(AppMessage) then) =
|
||||
_$AppMessageCopyWithImpl<$Res, AppMessage>;
|
||||
@useResult
|
||||
$Res call({MessageType type, dynamic data});
|
||||
$Res call({AppMessageType type, dynamic data});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$MessageCopyWithImpl<$Res, $Val extends Message>
|
||||
implements $MessageCopyWith<$Res> {
|
||||
_$MessageCopyWithImpl(this._value, this._then);
|
||||
class _$AppMessageCopyWithImpl<$Res, $Val extends AppMessage>
|
||||
implements $AppMessageCopyWith<$Res> {
|
||||
_$AppMessageCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
@@ -652,7 +654,7 @@ class _$MessageCopyWithImpl<$Res, $Val extends Message>
|
||||
type: null == type
|
||||
? _value.type
|
||||
: type // ignore: cast_nullable_to_non_nullable
|
||||
as MessageType,
|
||||
as AppMessageType,
|
||||
data: freezed == data
|
||||
? _value.data
|
||||
: data // ignore: cast_nullable_to_non_nullable
|
||||
@@ -662,21 +664,22 @@ class _$MessageCopyWithImpl<$Res, $Val extends Message>
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$MessageImplCopyWith<$Res> implements $MessageCopyWith<$Res> {
|
||||
factory _$$MessageImplCopyWith(
|
||||
_$MessageImpl value, $Res Function(_$MessageImpl) then) =
|
||||
__$$MessageImplCopyWithImpl<$Res>;
|
||||
abstract class _$$AppMessageImplCopyWith<$Res>
|
||||
implements $AppMessageCopyWith<$Res> {
|
||||
factory _$$AppMessageImplCopyWith(
|
||||
_$AppMessageImpl value, $Res Function(_$AppMessageImpl) then) =
|
||||
__$$AppMessageImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({MessageType type, dynamic data});
|
||||
$Res call({AppMessageType type, dynamic data});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$MessageImplCopyWithImpl<$Res>
|
||||
extends _$MessageCopyWithImpl<$Res, _$MessageImpl>
|
||||
implements _$$MessageImplCopyWith<$Res> {
|
||||
__$$MessageImplCopyWithImpl(
|
||||
_$MessageImpl _value, $Res Function(_$MessageImpl) _then)
|
||||
class __$$AppMessageImplCopyWithImpl<$Res>
|
||||
extends _$AppMessageCopyWithImpl<$Res, _$AppMessageImpl>
|
||||
implements _$$AppMessageImplCopyWith<$Res> {
|
||||
__$$AppMessageImplCopyWithImpl(
|
||||
_$AppMessageImpl _value, $Res Function(_$AppMessageImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@@ -685,11 +688,11 @@ class __$$MessageImplCopyWithImpl<$Res>
|
||||
Object? type = null,
|
||||
Object? data = freezed,
|
||||
}) {
|
||||
return _then(_$MessageImpl(
|
||||
return _then(_$AppMessageImpl(
|
||||
type: null == type
|
||||
? _value.type
|
||||
: type // ignore: cast_nullable_to_non_nullable
|
||||
as MessageType,
|
||||
as AppMessageType,
|
||||
data: freezed == data
|
||||
? _value.data
|
||||
: data // ignore: cast_nullable_to_non_nullable
|
||||
@@ -700,27 +703,27 @@ class __$$MessageImplCopyWithImpl<$Res>
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$MessageImpl implements _Message {
|
||||
const _$MessageImpl({required this.type, this.data});
|
||||
class _$AppMessageImpl implements _AppMessage {
|
||||
const _$AppMessageImpl({required this.type, this.data});
|
||||
|
||||
factory _$MessageImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$MessageImplFromJson(json);
|
||||
factory _$AppMessageImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$AppMessageImplFromJson(json);
|
||||
|
||||
@override
|
||||
final MessageType type;
|
||||
final AppMessageType type;
|
||||
@override
|
||||
final dynamic data;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'Message(type: $type, data: $data)';
|
||||
return 'AppMessage(type: $type, data: $data)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$MessageImpl &&
|
||||
other is _$AppMessageImpl &&
|
||||
(identical(other.type, type) || other.type == type) &&
|
||||
const DeepCollectionEquality().equals(other.data, data));
|
||||
}
|
||||
@@ -733,30 +736,188 @@ class _$MessageImpl implements _Message {
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$MessageImplCopyWith<_$MessageImpl> get copyWith =>
|
||||
__$$MessageImplCopyWithImpl<_$MessageImpl>(this, _$identity);
|
||||
_$$AppMessageImplCopyWith<_$AppMessageImpl> get copyWith =>
|
||||
__$$AppMessageImplCopyWithImpl<_$AppMessageImpl>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$$MessageImplToJson(
|
||||
return _$$AppMessageImplToJson(
|
||||
this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _Message implements Message {
|
||||
const factory _Message(
|
||||
{required final MessageType type, final dynamic data}) = _$MessageImpl;
|
||||
abstract class _AppMessage implements AppMessage {
|
||||
const factory _AppMessage(
|
||||
{required final AppMessageType type,
|
||||
final dynamic data}) = _$AppMessageImpl;
|
||||
|
||||
factory _Message.fromJson(Map<String, dynamic> json) = _$MessageImpl.fromJson;
|
||||
factory _AppMessage.fromJson(Map<String, dynamic> json) =
|
||||
_$AppMessageImpl.fromJson;
|
||||
|
||||
@override
|
||||
MessageType get type;
|
||||
AppMessageType get type;
|
||||
@override
|
||||
dynamic get data;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$MessageImplCopyWith<_$MessageImpl> get copyWith =>
|
||||
_$$AppMessageImplCopyWith<_$AppMessageImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
ServiceMessage _$ServiceMessageFromJson(Map<String, dynamic> json) {
|
||||
return _ServiceMessage.fromJson(json);
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$ServiceMessage {
|
||||
ServiceMessageType get type => throw _privateConstructorUsedError;
|
||||
dynamic get data => throw _privateConstructorUsedError;
|
||||
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@JsonKey(ignore: true)
|
||||
$ServiceMessageCopyWith<ServiceMessage> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $ServiceMessageCopyWith<$Res> {
|
||||
factory $ServiceMessageCopyWith(
|
||||
ServiceMessage value, $Res Function(ServiceMessage) then) =
|
||||
_$ServiceMessageCopyWithImpl<$Res, ServiceMessage>;
|
||||
@useResult
|
||||
$Res call({ServiceMessageType type, dynamic data});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$ServiceMessageCopyWithImpl<$Res, $Val extends ServiceMessage>
|
||||
implements $ServiceMessageCopyWith<$Res> {
|
||||
_$ServiceMessageCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? type = null,
|
||||
Object? data = freezed,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
type: null == type
|
||||
? _value.type
|
||||
: type // ignore: cast_nullable_to_non_nullable
|
||||
as ServiceMessageType,
|
||||
data: freezed == data
|
||||
? _value.data
|
||||
: data // ignore: cast_nullable_to_non_nullable
|
||||
as dynamic,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$ServiceMessageImplCopyWith<$Res>
|
||||
implements $ServiceMessageCopyWith<$Res> {
|
||||
factory _$$ServiceMessageImplCopyWith(_$ServiceMessageImpl value,
|
||||
$Res Function(_$ServiceMessageImpl) then) =
|
||||
__$$ServiceMessageImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({ServiceMessageType type, dynamic data});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$ServiceMessageImplCopyWithImpl<$Res>
|
||||
extends _$ServiceMessageCopyWithImpl<$Res, _$ServiceMessageImpl>
|
||||
implements _$$ServiceMessageImplCopyWith<$Res> {
|
||||
__$$ServiceMessageImplCopyWithImpl(
|
||||
_$ServiceMessageImpl _value, $Res Function(_$ServiceMessageImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? type = null,
|
||||
Object? data = freezed,
|
||||
}) {
|
||||
return _then(_$ServiceMessageImpl(
|
||||
type: null == type
|
||||
? _value.type
|
||||
: type // ignore: cast_nullable_to_non_nullable
|
||||
as ServiceMessageType,
|
||||
data: freezed == data
|
||||
? _value.data
|
||||
: data // ignore: cast_nullable_to_non_nullable
|
||||
as dynamic,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$ServiceMessageImpl implements _ServiceMessage {
|
||||
const _$ServiceMessageImpl({required this.type, this.data});
|
||||
|
||||
factory _$ServiceMessageImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$ServiceMessageImplFromJson(json);
|
||||
|
||||
@override
|
||||
final ServiceMessageType type;
|
||||
@override
|
||||
final dynamic data;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ServiceMessage(type: $type, data: $data)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$ServiceMessageImpl &&
|
||||
(identical(other.type, type) || other.type == type) &&
|
||||
const DeepCollectionEquality().equals(other.data, data));
|
||||
}
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
int get hashCode =>
|
||||
Object.hash(runtimeType, type, const DeepCollectionEquality().hash(data));
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$ServiceMessageImplCopyWith<_$ServiceMessageImpl> get copyWith =>
|
||||
__$$ServiceMessageImplCopyWithImpl<_$ServiceMessageImpl>(
|
||||
this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$$ServiceMessageImplToJson(
|
||||
this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _ServiceMessage implements ServiceMessage {
|
||||
const factory _ServiceMessage(
|
||||
{required final ServiceMessageType type,
|
||||
final dynamic data}) = _$ServiceMessageImpl;
|
||||
|
||||
factory _ServiceMessage.fromJson(Map<String, dynamic> json) =
|
||||
_$ServiceMessageImpl.fromJson;
|
||||
|
||||
@override
|
||||
ServiceMessageType get type;
|
||||
@override
|
||||
dynamic get data;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$ServiceMessageImplCopyWith<_$ServiceMessageImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
|
||||
@@ -55,27 +55,44 @@ Map<String, dynamic> _$$ChangeProxyParamsImplToJson(
|
||||
'proxy-name': instance.proxyName,
|
||||
};
|
||||
|
||||
_$MessageImpl _$$MessageImplFromJson(Map<String, dynamic> json) =>
|
||||
_$MessageImpl(
|
||||
type: $enumDecode(_$MessageTypeEnumMap, json['type']),
|
||||
_$AppMessageImpl _$$AppMessageImplFromJson(Map<String, dynamic> json) =>
|
||||
_$AppMessageImpl(
|
||||
type: $enumDecode(_$AppMessageTypeEnumMap, json['type']),
|
||||
data: json['data'],
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$MessageImplToJson(_$MessageImpl instance) =>
|
||||
Map<String, dynamic> _$$AppMessageImplToJson(_$AppMessageImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'type': _$MessageTypeEnumMap[instance.type]!,
|
||||
'type': _$AppMessageTypeEnumMap[instance.type]!,
|
||||
'data': instance.data,
|
||||
};
|
||||
|
||||
const _$MessageTypeEnumMap = {
|
||||
MessageType.log: 'log',
|
||||
MessageType.tun: 'tun',
|
||||
MessageType.delay: 'delay',
|
||||
MessageType.process: 'process',
|
||||
MessageType.now: 'now',
|
||||
MessageType.request: 'request',
|
||||
MessageType.run: 'run',
|
||||
MessageType.loaded: 'loaded',
|
||||
const _$AppMessageTypeEnumMap = {
|
||||
AppMessageType.log: 'log',
|
||||
AppMessageType.delay: 'delay',
|
||||
AppMessageType.request: 'request',
|
||||
AppMessageType.started: 'started',
|
||||
AppMessageType.loaded: 'loaded',
|
||||
};
|
||||
|
||||
_$ServiceMessageImpl _$$ServiceMessageImplFromJson(Map<String, dynamic> json) =>
|
||||
_$ServiceMessageImpl(
|
||||
type: $enumDecode(_$ServiceMessageTypeEnumMap, json['type']),
|
||||
data: json['data'],
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$ServiceMessageImplToJson(
|
||||
_$ServiceMessageImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'type': _$ServiceMessageTypeEnumMap[instance.type]!,
|
||||
'data': instance.data,
|
||||
};
|
||||
|
||||
const _$ServiceMessageTypeEnumMap = {
|
||||
ServiceMessageType.protect: 'protect',
|
||||
ServiceMessageType.process: 'process',
|
||||
ServiceMessageType.started: 'started',
|
||||
ServiceMessageType.loaded: 'loaded',
|
||||
};
|
||||
|
||||
_$DelayImpl _$$DelayImplFromJson(Map<String, dynamic> json) => _$DelayImpl(
|
||||
|
||||
@@ -5,32 +5,30 @@ import 'dart:isolate';
|
||||
|
||||
import 'package:fl_clash/clash/clash.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
class App {
|
||||
static App? _instance;
|
||||
MethodChannel? methodChannel;
|
||||
late MethodChannel methodChannel;
|
||||
Function()? onExit;
|
||||
|
||||
App._internal() {
|
||||
if (Platform.isAndroid) {
|
||||
methodChannel = const MethodChannel("app");
|
||||
methodChannel!.setMethodCallHandler((call) async {
|
||||
switch (call.method) {
|
||||
case "exit":
|
||||
if (onExit != null) {
|
||||
await onExit!();
|
||||
}
|
||||
break;
|
||||
case "gc":
|
||||
clashCore.requestGc();
|
||||
break;
|
||||
default:
|
||||
throw MissingPluginException();
|
||||
}
|
||||
});
|
||||
}
|
||||
methodChannel = const MethodChannel("app");
|
||||
methodChannel.setMethodCallHandler((call) async {
|
||||
switch (call.method) {
|
||||
case "exit":
|
||||
if (onExit != null) {
|
||||
await onExit!();
|
||||
}
|
||||
case "gc":
|
||||
clashCore.requestGc();
|
||||
default:
|
||||
throw MissingPluginException();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
factory App() {
|
||||
@@ -39,21 +37,21 @@ class App {
|
||||
}
|
||||
|
||||
Future<bool?> moveTaskToBack() async {
|
||||
return await methodChannel?.invokeMethod<bool>("moveTaskToBack");
|
||||
return await methodChannel.invokeMethod<bool>("moveTaskToBack");
|
||||
}
|
||||
|
||||
Future<List<Package>> getPackages() async {
|
||||
final packagesString =
|
||||
await methodChannel?.invokeMethod<String>("getPackages");
|
||||
await methodChannel.invokeMethod<String>("getPackages");
|
||||
return Isolate.run<List<Package>>(() {
|
||||
final List<dynamic> packagesRaw =
|
||||
packagesString != null ? json.decode(packagesString) : [];
|
||||
packagesString != null ? json.decode(packagesString) : [];
|
||||
return packagesRaw.map((e) => Package.fromJson(e)).toList();
|
||||
});
|
||||
}
|
||||
|
||||
Future<ImageProvider?> getPackageIcon(String packageName) async {
|
||||
final base64 = await methodChannel?.invokeMethod<String>("getPackageIcon", {
|
||||
final base64 = await methodChannel.invokeMethod<String>("getPackageIcon", {
|
||||
"packageName": packageName,
|
||||
});
|
||||
if (base64 == null) {
|
||||
@@ -63,19 +61,19 @@ class App {
|
||||
}
|
||||
|
||||
Future<bool?> tip(String? message) async {
|
||||
return await methodChannel?.invokeMethod<bool>("tip", {
|
||||
return await methodChannel.invokeMethod<bool>("tip", {
|
||||
"message": "$message",
|
||||
});
|
||||
}
|
||||
|
||||
Future<bool?> updateExcludeFromRecents(bool value) async {
|
||||
return await methodChannel?.invokeMethod<bool>("updateExcludeFromRecents", {
|
||||
return await methodChannel.invokeMethod<bool>("updateExcludeFromRecents", {
|
||||
"value": value,
|
||||
});
|
||||
}
|
||||
|
||||
Future<String?> resolverProcess(Process process) async {
|
||||
return await methodChannel?.invokeMethod<String>("resolverProcess", {
|
||||
return await methodChannel.invokeMethod<String>("resolverProcess", {
|
||||
"data": json.encode(process),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:ffi';
|
||||
import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
import 'package:fl_clash/clash/core.dart';
|
||||
import 'package:fl_clash/clash/clash.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/state.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:proxy/proxy_platform_interface.dart';
|
||||
|
||||
class Proxy extends ProxyPlatform {
|
||||
static Proxy? _instance;
|
||||
late MethodChannel methodChannel;
|
||||
late ReceivePort receiver;
|
||||
ReceivePort? receiver;
|
||||
ServiceMessageListener? _serviceMessageHandler;
|
||||
|
||||
Proxy._internal() {
|
||||
methodChannel = const MethodChannel("proxy");
|
||||
receiver = ReceivePort()
|
||||
..listen(
|
||||
(message) {
|
||||
setProtect(int.parse(message));
|
||||
},
|
||||
);
|
||||
methodChannel.setMethodCallHandler((call) async {
|
||||
switch (call.method) {
|
||||
case "startAfter":
|
||||
int fd = call.arguments;
|
||||
startAfterHook(fd);
|
||||
case "started":
|
||||
final fd = call.arguments;
|
||||
onStarted(fd);
|
||||
break;
|
||||
default:
|
||||
throw MissingPluginException();
|
||||
@@ -36,16 +36,27 @@ class Proxy extends ProxyPlatform {
|
||||
return _instance!;
|
||||
}
|
||||
|
||||
Future<bool?> _initService() async {
|
||||
return await methodChannel.invokeMethod<bool>("initService");
|
||||
}
|
||||
|
||||
handleStop() {
|
||||
globalState.stopSystemProxy();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool?> startProxy(int port, String? args) async {
|
||||
Future<bool?> startProxy(port, args) async {
|
||||
if (!globalState.isVpnService) {
|
||||
return await _initService();
|
||||
}
|
||||
return await methodChannel
|
||||
.invokeMethod<bool>("StartProxy", {'port': port, 'args': args});
|
||||
.invokeMethod<bool>("startProxy", {'port': port, 'args': args});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool?> stopProxy() async {
|
||||
clashCore.stopTun();
|
||||
final isStop = await methodChannel.invokeMethod<bool>("StopProxy");
|
||||
final isStop = await methodChannel.invokeMethod<bool>("stopProxy");
|
||||
if (isStop == true) {
|
||||
startTime = null;
|
||||
}
|
||||
@@ -53,11 +64,7 @@ class Proxy extends ProxyPlatform {
|
||||
}
|
||||
|
||||
Future<bool?> setProtect(int fd) async {
|
||||
return await methodChannel.invokeMethod<bool?>("SetProtect", {'fd': fd});
|
||||
}
|
||||
|
||||
Future<int?> getRunTimeStamp() async {
|
||||
return await methodChannel.invokeMethod<int?>("GetRunTimeStamp");
|
||||
return await methodChannel.invokeMethod<bool?>("setProtect", {'fd': fd});
|
||||
}
|
||||
|
||||
Future<bool?> startForeground({
|
||||
@@ -72,26 +79,43 @@ class Proxy extends ProxyPlatform {
|
||||
|
||||
bool get isStart => startTime != null && startTime!.isBeforeNow;
|
||||
|
||||
startAfterHook(int? fd) {
|
||||
if (!isStart && fd != null) {
|
||||
clashCore.startTun(fd);
|
||||
updateStartTime();
|
||||
onStarted(int? fd) {
|
||||
if (fd == null) return;
|
||||
if (receiver != null) {
|
||||
receiver!.close();
|
||||
receiver == null;
|
||||
}
|
||||
receiver = ReceivePort();
|
||||
receiver!.listen((message) {
|
||||
_handleServiceMessage(message);
|
||||
});
|
||||
clashCore.startTun(fd, receiver!.sendPort.nativePort);
|
||||
}
|
||||
|
||||
// updateStartTime() async {
|
||||
// startTime = clashCore.getRunTime();
|
||||
// }
|
||||
|
||||
updateStartTime() async {
|
||||
startTime = await getRunTime();
|
||||
updateStartTime() {
|
||||
startTime = clashCore.getRunTime();
|
||||
}
|
||||
|
||||
Future<DateTime?> getRunTime() async {
|
||||
final runTimeStamp = await getRunTimeStamp();
|
||||
return runTimeStamp != null
|
||||
? DateTime.fromMillisecondsSinceEpoch(runTimeStamp)
|
||||
: null;
|
||||
setServiceMessageHandler(ServiceMessageListener serviceMessageListener) {
|
||||
_serviceMessageHandler = serviceMessageListener;
|
||||
}
|
||||
|
||||
_handleServiceMessage(String message) {
|
||||
final m = ServiceMessage.fromJson(json.decode(message));
|
||||
switch (m.type) {
|
||||
case ServiceMessageType.protect:
|
||||
_serviceMessageHandler?.onProtect(Fd.fromJson(m.data));
|
||||
break;
|
||||
case ServiceMessageType.process:
|
||||
_serviceMessageHandler?.onProcess(Process.fromJson(m.data));
|
||||
break;
|
||||
case ServiceMessageType.started:
|
||||
_serviceMessageHandler?.onStarted(m.data);
|
||||
break;
|
||||
case ServiceMessageType.loaded:
|
||||
_serviceMessageHandler?.onLoaded(m.data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,17 +5,20 @@ import 'dart:io';
|
||||
import 'package:animations/animations.dart';
|
||||
import 'package:fl_clash/clash/clash.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/plugins/proxy.dart';
|
||||
import 'package:fl_clash/widgets/scaffold.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
|
||||
import 'controller.dart';
|
||||
|
||||
import 'models/models.dart';
|
||||
import 'common/common.dart';
|
||||
|
||||
class GlobalState {
|
||||
Timer? timer;
|
||||
Timer? groupsUpdateTimer;
|
||||
var isVpnService = false;
|
||||
late PackageInfo packageInfo;
|
||||
Function? updateCurrentDelayDebounce;
|
||||
PageController? pageController;
|
||||
@@ -82,12 +85,15 @@ class GlobalState {
|
||||
args: args,
|
||||
);
|
||||
startListenUpdate();
|
||||
if (Platform.isAndroid) {
|
||||
return;
|
||||
}
|
||||
applyProfile(
|
||||
appState: appState,
|
||||
config: config,
|
||||
clashConfig: clashConfig,
|
||||
).then((_){
|
||||
appController.addCheckIpNumDebounce();
|
||||
globalState.appController.addCheckIpNumDebounce();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -106,6 +112,7 @@ class GlobalState {
|
||||
config: config,
|
||||
isPatch: false,
|
||||
);
|
||||
clashCore.setProfileName(config.currentProfile?.label ?? '');
|
||||
await updateGroups(appState);
|
||||
}
|
||||
|
||||
@@ -182,20 +189,18 @@ class GlobalState {
|
||||
|
||||
updateTraffic({
|
||||
AppState? appState,
|
||||
required Config config,
|
||||
}) {
|
||||
final traffic = clashCore.getTraffic();
|
||||
if (appState != null) {
|
||||
appState.addTraffic(traffic);
|
||||
appState.totalTraffic = clashCore.getTotalTraffic();
|
||||
}
|
||||
if (Platform.isAndroid) {
|
||||
final currentProfile = config.currentProfile;
|
||||
if (currentProfile == null) return;
|
||||
proxyManager.startForeground(
|
||||
title: currentProfile.label ?? currentProfile.id,
|
||||
if (Platform.isAndroid && isVpnService == true) {
|
||||
proxy?.startForeground(
|
||||
title: clashCore.getProfileName(),
|
||||
content: "$traffic",
|
||||
);
|
||||
} else {
|
||||
if (appState != null) {
|
||||
appState.addTraffic(traffic);
|
||||
appState.totalTraffic = clashCore.getTotalTraffic();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ class AndroidContainer extends StatefulWidget {
|
||||
|
||||
class _AndroidContainerState extends State<AndroidContainer>
|
||||
with WidgetsBindingObserver {
|
||||
|
||||
_excludeContainer(Widget child) {
|
||||
return Selector<Config, bool>(
|
||||
selector: (_, config) => config.isExclude,
|
||||
@@ -47,9 +48,7 @@ class _AndroidContainerState extends State<AndroidContainer>
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return _excludeContainer(
|
||||
widget.child,
|
||||
);
|
||||
return _excludeContainer(widget.child);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:fl_clash/clash/clash.dart';
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
import 'package:fl_clash/plugins/proxy.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:fl_clash/plugins/app.dart';
|
||||
@@ -18,7 +18,7 @@ class ClashMessageContainer extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _ClashMessageContainerState extends State<ClashMessageContainer>
|
||||
with ClashMessageListener {
|
||||
with AppMessageListener {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return widget.child;
|
||||
@@ -49,25 +49,6 @@ class _ClashMessageContainerState extends State<ClashMessageContainer>
|
||||
super.onLog(log);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onTun(Fd fd) async {
|
||||
await proxyManager.setProtect(fd.value);
|
||||
clashCore.setFdMap(fd.id);
|
||||
super.onTun(fd);
|
||||
}
|
||||
|
||||
@override
|
||||
void onProcess(Process process) async {
|
||||
var packageName = await app?.resolverProcess(process);
|
||||
clashCore.setProcessMap(
|
||||
ProcessMapItem(
|
||||
id: process.id,
|
||||
value: packageName ?? "",
|
||||
),
|
||||
);
|
||||
super.onProcess(process);
|
||||
}
|
||||
|
||||
@override
|
||||
void onRequest(Connection connection) async {
|
||||
globalState.appController.appState.addRequest(connection);
|
||||
@@ -89,4 +70,14 @@ class _ClashMessageContainerState extends State<ClashMessageContainer>
|
||||
appController.addCheckIpNumDebounce();
|
||||
super.onLoaded(proxyName);
|
||||
}
|
||||
|
||||
@override
|
||||
void onStarted(String runTime) {
|
||||
super.onStarted(runTime);
|
||||
proxy?.updateStartTime();
|
||||
final appController = globalState.appController;
|
||||
appController.rawApplyProfile().then((_) {
|
||||
appController.addCheckIpNumDebounce();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
name: fl_clash
|
||||
description: A multi-platform proxy client based on ClashMeta, simple and easy to use, open-source and ad-free.
|
||||
publish_to: 'none'
|
||||
version: 0.8.36+202407081
|
||||
version: 0.8.36+202407131
|
||||
environment:
|
||||
sdk: '>=3.1.0 <4.0.0'
|
||||
|
||||
|
||||
Reference in New Issue
Block a user