Remake desktop
Optimize change proxy Optimize network check Fix fallback issues Optimize lots of details
This commit is contained in:
20
.github/workflows/build.yaml
vendored
20
.github/workflows/build.yaml
vendored
@@ -27,25 +27,6 @@ jobs:
|
|||||||
arch: arm64
|
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:
|
||||||
@@ -103,7 +84,6 @@ jobs:
|
|||||||
path: ./dist
|
path: ./dist
|
||||||
overwrite: true
|
overwrite: true
|
||||||
|
|
||||||
|
|
||||||
upload:
|
upload:
|
||||||
permissions: write-all
|
permissions: write-all
|
||||||
needs: [ build ]
|
needs: [ build ]
|
||||||
|
|||||||
8
.github/workflows/change.yaml
vendored
8
.github/workflows/change.yaml
vendored
@@ -1,12 +1,13 @@
|
|||||||
name: change
|
name: changelog
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
tags:
|
||||||
- 'main'
|
- 'v*'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
changelog:
|
changelog:
|
||||||
|
if: ${{ !contains(github.ref, '+') }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
@@ -61,6 +62,5 @@ jobs:
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
Submodule core/Clash.Meta updated: 148f1a2445...f7c61f885c
32
core/action.go
Normal file
32
core/action.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
//go:build !cgo
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (action Action) Json() ([]byte, error) {
|
||||||
|
data, err := json.Marshal(action)
|
||||||
|
return data, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (action Action) callback(data interface{}) bool {
|
||||||
|
if conn == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
sendAction := Action{
|
||||||
|
Id: action.Id,
|
||||||
|
Method: action.Method,
|
||||||
|
Data: data,
|
||||||
|
}
|
||||||
|
res, err := sendAction.Json()
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
_, err = conn.Write(append(res, []byte("\n")...))
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
154
core/common.go
154
core/common.go
@@ -1,23 +1,10 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import "C"
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"core/state"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/metacubex/mihomo/constant/features"
|
|
||||||
"github.com/metacubex/mihomo/hub/route"
|
|
||||||
"github.com/samber/lo"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/metacubex/mihomo/adapter"
|
"github.com/metacubex/mihomo/adapter"
|
||||||
"github.com/metacubex/mihomo/adapter/inbound"
|
"github.com/metacubex/mihomo/adapter/inbound"
|
||||||
"github.com/metacubex/mihomo/adapter/outboundgroup"
|
"github.com/metacubex/mihomo/adapter/outboundgroup"
|
||||||
@@ -27,53 +14,28 @@ import (
|
|||||||
"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"
|
||||||
|
"github.com/metacubex/mihomo/constant/features"
|
||||||
cp "github.com/metacubex/mihomo/constant/provider"
|
cp "github.com/metacubex/mihomo/constant/provider"
|
||||||
"github.com/metacubex/mihomo/hub"
|
"github.com/metacubex/mihomo/hub"
|
||||||
"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"
|
||||||
rp "github.com/metacubex/mihomo/rules/provider"
|
rp "github.com/metacubex/mihomo/rules/provider"
|
||||||
"github.com/metacubex/mihomo/tunnel"
|
"github.com/metacubex/mihomo/tunnel"
|
||||||
|
"github.com/samber/lo"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ConfigExtendedParams struct {
|
var (
|
||||||
IsPatch bool `json:"is-patch"`
|
isRunning = false
|
||||||
IsCompatible bool `json:"is-compatible"`
|
runLock sync.Mutex
|
||||||
SelectedMap map[string]string `json:"selected-map"`
|
ips = []string{"ipinfo.io", "ipapi.co", "api.ip.sb", "ipwho.is"}
|
||||||
TestURL *string `json:"test-url"`
|
b, _ = batch.New[bool](context.Background(), batch.WithConcurrencyNum[bool](50))
|
||||||
OverrideDns bool `json:"override-dns"`
|
)
|
||||||
}
|
|
||||||
|
|
||||||
type GenerateConfigParams struct {
|
|
||||||
ProfileId string `json:"profile-id"`
|
|
||||||
Config config.RawConfig `json:"config" `
|
|
||||||
Params ConfigExtendedParams `json:"params"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ChangeProxyParams struct {
|
|
||||||
GroupName *string `json:"group-name"`
|
|
||||||
ProxyName *string `json:"proxy-name"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type TestDelayParams struct {
|
|
||||||
ProxyName string `json:"proxy-name"`
|
|
||||||
Timeout int64 `json:"timeout"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ProcessMapItem struct {
|
|
||||||
Id int64 `json:"id"`
|
|
||||||
Value string `json:"value"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ExternalProvider struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Type string `json:"type"`
|
|
||||||
VehicleType string `json:"vehicle-type"`
|
|
||||||
Count int `json:"count"`
|
|
||||||
Path string `json:"path"`
|
|
||||||
UpdateAt time.Time `json:"update-at"`
|
|
||||||
SubscriptionInfo *provider.SubscriptionInfo `json:"subscription-info"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ExternalProviders []ExternalProvider
|
type ExternalProviders []ExternalProvider
|
||||||
|
|
||||||
@@ -81,30 +43,9 @@ func (a ExternalProviders) Len() int { return len(a) }
|
|||||||
func (a ExternalProviders) Less(i, j int) bool { return a[i].Name < a[j].Name }
|
func (a ExternalProviders) Less(i, j int) bool { return a[i].Name < a[j].Name }
|
||||||
func (a ExternalProviders) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
func (a ExternalProviders) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||||
|
|
||||||
var b, _ = batch.New[bool](context.Background(), batch.WithConcurrencyNum[bool](50))
|
func (message *Message) Json() (string, error) {
|
||||||
|
data, err := json.Marshal(message)
|
||||||
func restartExecutable(execPath string) {
|
return string(data), err
|
||||||
var err error
|
|
||||||
executor.Shutdown()
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
cmd := exec.Command(execPath, os.Args[1:]...)
|
|
||||||
log.Infoln("restarting: %q %q", execPath, os.Args[1:])
|
|
||||||
cmd.Stdin = os.Stdin
|
|
||||||
cmd.Stdout = os.Stdout
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
err = cmd.Start()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln("restarting: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Infoln("restarting: %q %q", execPath, os.Args[1:])
|
|
||||||
err = syscall.Exec(execPath, os.Args, os.Environ())
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln("restarting: %s", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func readFile(path string) ([]byte, error) {
|
func readFile(path string) ([]byte, error) {
|
||||||
@@ -119,19 +60,6 @@ func readFile(path string) ([]byte, error) {
|
|||||||
return data, err
|
return data, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func removeFile(path string) error {
|
|
||||||
absPath, err := filepath.Abs(path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = os.Remove(absPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getProfilePath(id string) string {
|
func getProfilePath(id string) string {
|
||||||
return filepath.Join(constant.Path.HomeDir(), "profiles", id+".yaml")
|
return filepath.Join(constant.Path.HomeDir(), "profiles", id+".yaml")
|
||||||
}
|
}
|
||||||
@@ -262,8 +190,6 @@ func trimArr(arr []string) (r []string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var ips = []string{"ipinfo.io", "ipapi.co", "api.ip.sb", "ipwho.is"}
|
|
||||||
|
|
||||||
func overrideRules(rules *[]string) {
|
func overrideRules(rules *[]string) {
|
||||||
var target = ""
|
var target = ""
|
||||||
for _, line := range *rules {
|
for _, line := range *rules {
|
||||||
@@ -325,20 +251,13 @@ func overwriteConfig(targetConfig *config.RawConfig, patchConfig config.RawConfi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
overrideRules(&targetConfig.Rule)
|
overrideRules(&targetConfig.Rule)
|
||||||
//if runtime.GOOS == "android" {
|
|
||||||
// targetConfig.DNS.NameServer = append(targetConfig.DNS.NameServer, "dhcp://"+dns.SystemDNSPlaceholder)
|
|
||||||
//} else if runtime.GOOS == "windows" {
|
|
||||||
// targetConfig.DNS.NameServer = append(targetConfig.DNS.NameServer, dns.SystemDNSPlaceholder)
|
|
||||||
//}
|
|
||||||
//if configParams.IsCompatible == false {
|
|
||||||
// targetConfig.ProxyProvider = make(map[string]map[string]any)
|
|
||||||
// targetConfig.RuleProvider = make(map[string]map[string]any)
|
|
||||||
// generateProxyGroupAndRule(&targetConfig.ProxyGroup, &targetConfig.Rule)
|
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func patchConfig(general *config.General, controller *config.Controller, tls *config.TLS) {
|
func patchConfig() {
|
||||||
log.Infoln("[Apply] patch")
|
log.Infoln("[Apply] patch")
|
||||||
|
general := currentConfig.General
|
||||||
|
controller := currentConfig.Controller
|
||||||
|
tls := currentConfig.TLS
|
||||||
tunnel.SetSniffing(general.Sniffing)
|
tunnel.SetSniffing(general.Sniffing)
|
||||||
tunnel.SetFindProcessMode(general.FindProcessMode)
|
tunnel.SetFindProcessMode(general.FindProcessMode)
|
||||||
dialer.SetTcpConcurrent(general.TCPConcurrent)
|
dialer.SetTcpConcurrent(general.TCPConcurrent)
|
||||||
@@ -365,17 +284,15 @@ func patchConfig(general *config.General, controller *config.Controller, tls *co
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
var isRunning = false
|
func updateListeners(force bool) {
|
||||||
|
|
||||||
var runLock sync.Mutex
|
|
||||||
|
|
||||||
func updateListeners(general *config.General, listeners map[string]constant.InboundListener) {
|
|
||||||
if !isRunning {
|
if !isRunning {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
runLock.Lock()
|
general := currentConfig.General
|
||||||
defer runLock.Unlock()
|
listeners := currentConfig.Listeners
|
||||||
|
if force == true {
|
||||||
stopListeners()
|
stopListeners()
|
||||||
|
}
|
||||||
listener.PatchInboundListeners(listeners, tunnel.Tunnel, true)
|
listener.PatchInboundListeners(listeners, tunnel.Tunnel, true)
|
||||||
listener.SetAllowLan(general.AllowLan)
|
listener.SetAllowLan(general.AllowLan)
|
||||||
inbound.SetSkipAuthPrefixes(general.SkipAuthPrefixes)
|
inbound.SetSkipAuthPrefixes(general.SkipAuthPrefixes)
|
||||||
@@ -424,19 +341,22 @@ func patchSelectGroup() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyConfig() error {
|
func applyConfig(rawConfig *config.RawConfig) error {
|
||||||
cfg, err := config.ParseRawConfig(state.CurrentRawConfig)
|
runLock.Lock()
|
||||||
|
defer runLock.Unlock()
|
||||||
|
var err error
|
||||||
|
currentConfig, err = config.ParseRawConfig(rawConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cfg, _ = config.ParseRawConfig(config.DefaultRawConfig())
|
currentConfig, _ = config.ParseRawConfig(config.DefaultRawConfig())
|
||||||
}
|
}
|
||||||
if configParams.IsPatch {
|
if configParams.IsPatch {
|
||||||
patchConfig(cfg.General, cfg.Controller, cfg.TLS)
|
patchConfig()
|
||||||
} else {
|
} else {
|
||||||
closeConnections()
|
handleCloseConnectionsUnLock()
|
||||||
runtime.GC()
|
runtime.GC()
|
||||||
hub.ApplyConfig(cfg)
|
hub.ApplyConfig(currentConfig)
|
||||||
patchSelectGroup()
|
patchSelectGroup()
|
||||||
}
|
}
|
||||||
updateListeners(cfg.General, cfg.Listeners)
|
updateListeners(false)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
110
core/constant.go
Normal file
110
core/constant.go
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/metacubex/mihomo/adapter/provider"
|
||||||
|
"github.com/metacubex/mihomo/config"
|
||||||
|
"github.com/metacubex/mihomo/constant"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ConfigExtendedParams struct {
|
||||||
|
IsPatch bool `json:"is-patch"`
|
||||||
|
IsCompatible bool `json:"is-compatible"`
|
||||||
|
SelectedMap map[string]string `json:"selected-map"`
|
||||||
|
TestURL *string `json:"test-url"`
|
||||||
|
OverrideDns bool `json:"override-dns"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GenerateConfigParams struct {
|
||||||
|
ProfileId string `json:"profile-id"`
|
||||||
|
Config config.RawConfig `json:"config" `
|
||||||
|
Params ConfigExtendedParams `json:"params"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChangeProxyParams struct {
|
||||||
|
GroupName *string `json:"group-name"`
|
||||||
|
ProxyName *string `json:"proxy-name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TestDelayParams struct {
|
||||||
|
ProxyName string `json:"proxy-name"`
|
||||||
|
Timeout int64 `json:"timeout"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProcessMapItem struct {
|
||||||
|
Id int64 `json:"id"`
|
||||||
|
Value string `json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExternalProvider struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
VehicleType string `json:"vehicle-type"`
|
||||||
|
Count int `json:"count"`
|
||||||
|
Path string `json:"path"`
|
||||||
|
UpdateAt time.Time `json:"update-at"`
|
||||||
|
SubscriptionInfo *provider.SubscriptionInfo `json:"subscription-info"`
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
messageMethod Method = "message"
|
||||||
|
initClashMethod Method = "initClash"
|
||||||
|
getIsInitMethod Method = "getIsInit"
|
||||||
|
forceGcMethod Method = "forceGc"
|
||||||
|
shutdownMethod Method = "shutdown"
|
||||||
|
validateConfigMethod Method = "validateConfig"
|
||||||
|
updateConfigMethod Method = "updateConfig"
|
||||||
|
getProxiesMethod Method = "getProxies"
|
||||||
|
changeProxyMethod Method = "changeProxy"
|
||||||
|
getTrafficMethod Method = "getTraffic"
|
||||||
|
getTotalTrafficMethod Method = "getTotalTraffic"
|
||||||
|
resetTrafficMethod Method = "resetTraffic"
|
||||||
|
asyncTestDelayMethod Method = "asyncTestDelay"
|
||||||
|
getConnectionsMethod Method = "getConnections"
|
||||||
|
closeConnectionsMethod Method = "closeConnections"
|
||||||
|
closeConnectionMethod Method = "closeConnection"
|
||||||
|
getExternalProvidersMethod Method = "getExternalProviders"
|
||||||
|
getExternalProviderMethod Method = "getExternalProvider"
|
||||||
|
updateGeoDataMethod Method = "updateGeoData"
|
||||||
|
updateExternalProviderMethod Method = "updateExternalProvider"
|
||||||
|
sideLoadExternalProviderMethod Method = "sideLoadExternalProvider"
|
||||||
|
startLogMethod Method = "startLog"
|
||||||
|
stopLogMethod Method = "stopLog"
|
||||||
|
startListenerMethod Method = "startListener"
|
||||||
|
stopListenerMethod Method = "stopListener"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Method string
|
||||||
|
|
||||||
|
type Action struct {
|
||||||
|
Id string `json:"id"`
|
||||||
|
Method Method `json:"method"`
|
||||||
|
Data interface{} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MessageType string
|
||||||
|
|
||||||
|
type Delay struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Value int32 `json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Message struct {
|
||||||
|
Type MessageType `json:"type"`
|
||||||
|
Data interface{} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Process struct {
|
||||||
|
Id int64 `json:"id"`
|
||||||
|
Metadata *constant.Metadata `json:"metadata"`
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
LogMessage MessageType = "log"
|
||||||
|
ProtectMessage MessageType = "protect"
|
||||||
|
DelayMessage MessageType = "delay"
|
||||||
|
ProcessMessage MessageType = "process"
|
||||||
|
RequestMessage MessageType = "request"
|
||||||
|
StartedMessage MessageType = "started"
|
||||||
|
LoadedMessage MessageType = "loaded"
|
||||||
|
)
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
//go:build cgo
|
||||||
|
|
||||||
package dart_bridge
|
package dart_bridge
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
7
core/dart-bridge/lib_common.go
Normal file
7
core/dart-bridge/lib_common.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
//go:build !cgo
|
||||||
|
|
||||||
|
package dart_bridge
|
||||||
|
|
||||||
|
func SendToPort(port int64, msg string) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
20
core/dns.go
20
core/dns.go
@@ -1,20 +0,0 @@
|
|||||||
//go:build android
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import "C"
|
|
||||||
import (
|
|
||||||
"github.com/metacubex/mihomo/dns"
|
|
||||||
"github.com/metacubex/mihomo/log"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
//export updateDns
|
|
||||||
func updateDns(s *C.char) {
|
|
||||||
dnsList := C.GoString(s)
|
|
||||||
go func() {
|
|
||||||
log.Infoln("[DNS] updateDns %s", dnsList)
|
|
||||||
dns.UpdateSystemDNS(strings.Split(dnsList, ","))
|
|
||||||
dns.FlushCacheWithDefaultResolver()
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
module core
|
module core
|
||||||
|
|
||||||
go 1.21.0
|
go 1.21
|
||||||
|
|
||||||
replace github.com/metacubex/mihomo => ./Clash.Meta
|
replace github.com/metacubex/mihomo => ./Clash.Meta
|
||||||
|
|
||||||
|
|||||||
433
core/hub.go
433
core/hub.go
@@ -1 +1,434 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/metacubex/mihomo/adapter"
|
||||||
|
"github.com/metacubex/mihomo/adapter/outboundgroup"
|
||||||
|
"github.com/metacubex/mihomo/common/observable"
|
||||||
|
"github.com/metacubex/mihomo/common/utils"
|
||||||
|
"github.com/metacubex/mihomo/component/updater"
|
||||||
|
"github.com/metacubex/mihomo/config"
|
||||||
|
"github.com/metacubex/mihomo/constant"
|
||||||
|
cp "github.com/metacubex/mihomo/constant/provider"
|
||||||
|
"github.com/metacubex/mihomo/hub/executor"
|
||||||
|
"github.com/metacubex/mihomo/listener"
|
||||||
|
"github.com/metacubex/mihomo/log"
|
||||||
|
"github.com/metacubex/mihomo/tunnel"
|
||||||
|
"github.com/metacubex/mihomo/tunnel/statistic"
|
||||||
|
"runtime"
|
||||||
|
"sort"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
isInit = false
|
||||||
|
configParams = ConfigExtendedParams{}
|
||||||
|
externalProviders = map[string]cp.Provider{}
|
||||||
|
logSubscriber observable.Subscription[log.Event]
|
||||||
|
currentConfig *config.Config
|
||||||
|
)
|
||||||
|
|
||||||
|
func handleInitClash(homeDirStr string) bool {
|
||||||
|
if !isInit {
|
||||||
|
constant.SetHomeDir(homeDirStr)
|
||||||
|
isInit = true
|
||||||
|
}
|
||||||
|
return isInit
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleStartListener() bool {
|
||||||
|
runLock.Lock()
|
||||||
|
defer runLock.Unlock()
|
||||||
|
isRunning = true
|
||||||
|
updateListeners(true)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleStopListener() bool {
|
||||||
|
runLock.Lock()
|
||||||
|
defer runLock.Unlock()
|
||||||
|
isRunning = false
|
||||||
|
listener.StopListener()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleGetIsInit() bool {
|
||||||
|
return isInit
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleForceGc() {
|
||||||
|
go func() {
|
||||||
|
log.Infoln("[APP] request force GC")
|
||||||
|
runtime.GC()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleShutdown() bool {
|
||||||
|
stopListeners()
|
||||||
|
executor.Shutdown()
|
||||||
|
runtime.GC()
|
||||||
|
isInit = false
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleValidateConfig(bytes []byte) string {
|
||||||
|
_, err := config.UnmarshalRawConfig(bytes)
|
||||||
|
if err != nil {
|
||||||
|
return err.Error()
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleUpdateConfig(bytes []byte) string {
|
||||||
|
var params = &GenerateConfigParams{}
|
||||||
|
err := json.Unmarshal(bytes, params)
|
||||||
|
if err != nil {
|
||||||
|
return err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
configParams = params.Params
|
||||||
|
prof := decorationConfig(params.ProfileId, params.Config)
|
||||||
|
err = applyConfig(prof)
|
||||||
|
if err != nil {
|
||||||
|
return err.Error()
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleGetProxies() string {
|
||||||
|
runLock.Lock()
|
||||||
|
defer runLock.Unlock()
|
||||||
|
data, err := json.Marshal(tunnel.ProxiesWithProviders())
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return string(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleChangeProxy(data string, fn func(string string)) {
|
||||||
|
runLock.Lock()
|
||||||
|
go func() {
|
||||||
|
defer runLock.Unlock()
|
||||||
|
var params = &ChangeProxyParams{}
|
||||||
|
err := json.Unmarshal([]byte(data), params)
|
||||||
|
if err != nil {
|
||||||
|
fn(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
groupName := *params.GroupName
|
||||||
|
proxyName := *params.ProxyName
|
||||||
|
proxies := tunnel.ProxiesWithProviders()
|
||||||
|
group, ok := proxies[groupName]
|
||||||
|
if !ok {
|
||||||
|
fn("Not found group")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
adapterProxy := group.(*adapter.Proxy)
|
||||||
|
selector, ok := adapterProxy.ProxyAdapter.(outboundgroup.SelectAble)
|
||||||
|
if !ok {
|
||||||
|
fn("Group is not selectable")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if proxyName == "" {
|
||||||
|
selector.ForceSet(proxyName)
|
||||||
|
} else {
|
||||||
|
err = selector.Set(proxyName)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
fn(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fn("")
|
||||||
|
return
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleGetTraffic(onlyProxy bool) string {
|
||||||
|
up, down := statistic.DefaultManager.Current(onlyProxy)
|
||||||
|
traffic := map[string]int64{
|
||||||
|
"up": up,
|
||||||
|
"down": down,
|
||||||
|
}
|
||||||
|
data, err := json.Marshal(traffic)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error:", err)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return string(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleGetTotalTraffic(onlyProxy bool) string {
|
||||||
|
up, down := statistic.DefaultManager.Total(onlyProxy)
|
||||||
|
traffic := map[string]int64{
|
||||||
|
"up": up,
|
||||||
|
"down": down,
|
||||||
|
}
|
||||||
|
data, err := json.Marshal(traffic)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error:", err)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return string(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleResetTraffic() {
|
||||||
|
statistic.DefaultManager.ResetStatistic()
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleAsyncTestDelay(paramsString string, fn func(string)) {
|
||||||
|
b.Go(paramsString, func() (bool, error) {
|
||||||
|
var params = &TestDelayParams{}
|
||||||
|
err := json.Unmarshal([]byte(paramsString), params)
|
||||||
|
if err != nil {
|
||||||
|
fn("")
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedStatus, err := utils.NewUnsignedRanges[uint16]("")
|
||||||
|
if err != nil {
|
||||||
|
fn("")
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(params.Timeout))
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
proxies := tunnel.ProxiesWithProviders()
|
||||||
|
proxy := proxies[params.ProxyName]
|
||||||
|
|
||||||
|
delayData := &Delay{
|
||||||
|
Name: params.ProxyName,
|
||||||
|
}
|
||||||
|
|
||||||
|
if proxy == nil {
|
||||||
|
delayData.Value = -1
|
||||||
|
data, _ := json.Marshal(delayData)
|
||||||
|
fn(string(data))
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
delay, err := proxy.URLTest(ctx, constant.DefaultTestURL, expectedStatus)
|
||||||
|
if err != nil || delay == 0 {
|
||||||
|
delayData.Value = -1
|
||||||
|
data, _ := json.Marshal(delayData)
|
||||||
|
fn(string(data))
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
delayData.Value = int32(delay)
|
||||||
|
data, _ := json.Marshal(delayData)
|
||||||
|
fn(string(data))
|
||||||
|
return false, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleGetConnections() string {
|
||||||
|
runLock.Lock()
|
||||||
|
defer runLock.Unlock()
|
||||||
|
snapshot := statistic.DefaultManager.Snapshot()
|
||||||
|
data, err := json.Marshal(snapshot)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error:", err)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return string(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleCloseConnectionsUnLock() bool {
|
||||||
|
statistic.DefaultManager.Range(func(c statistic.Tracker) bool {
|
||||||
|
err := c.Close()
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleCloseConnections() bool {
|
||||||
|
runLock.Lock()
|
||||||
|
defer runLock.Unlock()
|
||||||
|
statistic.DefaultManager.Range(func(c statistic.Tracker) bool {
|
||||||
|
err := c.Close()
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleCloseConnection(connectionId string) bool {
|
||||||
|
runLock.Lock()
|
||||||
|
defer runLock.Unlock()
|
||||||
|
c := statistic.DefaultManager.Get(connectionId)
|
||||||
|
if c == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
_ = c.Close()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleGetExternalProviders() string {
|
||||||
|
runLock.Lock()
|
||||||
|
defer runLock.Unlock()
|
||||||
|
externalProviders = getExternalProvidersRaw()
|
||||||
|
eps := make([]ExternalProvider, 0)
|
||||||
|
for _, p := range externalProviders {
|
||||||
|
externalProvider, err := toExternalProvider(p)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
eps = append(eps, *externalProvider)
|
||||||
|
}
|
||||||
|
sort.Sort(ExternalProviders(eps))
|
||||||
|
data, err := json.Marshal(eps)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return string(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleGetExternalProvider(externalProviderName string) string {
|
||||||
|
runLock.Lock()
|
||||||
|
defer runLock.Unlock()
|
||||||
|
externalProvider, exist := externalProviders[externalProviderName]
|
||||||
|
if !exist {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
e, err := toExternalProvider(externalProvider)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
data, err := json.Marshal(e)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return string(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleUpdateGeoData(geoType string, geoName string, fn func(value string)) {
|
||||||
|
go func() {
|
||||||
|
path := constant.Path.Resolve(geoName)
|
||||||
|
switch geoType {
|
||||||
|
case "MMDB":
|
||||||
|
err := updater.UpdateMMDBWithPath(path)
|
||||||
|
if err != nil {
|
||||||
|
fn(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "ASN":
|
||||||
|
err := updater.UpdateASNWithPath(path)
|
||||||
|
if err != nil {
|
||||||
|
fn(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "GeoIp":
|
||||||
|
err := updater.UpdateGeoIpWithPath(path)
|
||||||
|
if err != nil {
|
||||||
|
fn(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "GeoSite":
|
||||||
|
err := updater.UpdateGeoSiteWithPath(path)
|
||||||
|
if err != nil {
|
||||||
|
fn(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn("")
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleUpdateExternalProvider(providerName string, fn func(value string)) {
|
||||||
|
go func() {
|
||||||
|
externalProvider, exist := externalProviders[providerName]
|
||||||
|
if !exist {
|
||||||
|
fn("external provider is not exist")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err := externalProvider.Update()
|
||||||
|
if err != nil {
|
||||||
|
fn(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fn("")
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleSideLoadExternalProvider(providerName string, data []byte, fn func(value string)) {
|
||||||
|
go func() {
|
||||||
|
runLock.Lock()
|
||||||
|
defer runLock.Unlock()
|
||||||
|
externalProvider, exist := externalProviders[providerName]
|
||||||
|
if !exist {
|
||||||
|
fn("external provider is not exist")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err := sideUpdateExternalProvider(externalProvider, data)
|
||||||
|
if err != nil {
|
||||||
|
fn(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fn("")
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleStartLog() {
|
||||||
|
if logSubscriber != nil {
|
||||||
|
log.UnSubscribe(logSubscriber)
|
||||||
|
logSubscriber = nil
|
||||||
|
}
|
||||||
|
logSubscriber = log.Subscribe()
|
||||||
|
go func() {
|
||||||
|
for logData := range logSubscriber {
|
||||||
|
if logData.LogLevel < log.Level() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
message := &Message{
|
||||||
|
Type: LogMessage,
|
||||||
|
Data: logData,
|
||||||
|
}
|
||||||
|
SendMessage(*message)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleStopLog() {
|
||||||
|
if logSubscriber != nil {
|
||||||
|
log.UnSubscribe(logSubscriber)
|
||||||
|
logSubscriber = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
adapter.UrlTestHook = func(name string, delay uint16) {
|
||||||
|
delayData := &Delay{
|
||||||
|
Name: name,
|
||||||
|
}
|
||||||
|
if delay == 0 {
|
||||||
|
delayData.Value = -1
|
||||||
|
} else {
|
||||||
|
delayData.Value = int32(delay)
|
||||||
|
}
|
||||||
|
SendMessage(Message{
|
||||||
|
Type: DelayMessage,
|
||||||
|
Data: delayData,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
statistic.DefaultRequestNotify = func(c statistic.Tracker) {
|
||||||
|
SendMessage(Message{
|
||||||
|
Type: RequestMessage,
|
||||||
|
Data: c,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
executor.DefaultProviderLoadedHook = func(providerName string) {
|
||||||
|
SendMessage(Message{
|
||||||
|
Type: LoadedMessage,
|
||||||
|
Data: providerName,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
585
core/lib.go
585
core/lib.go
@@ -1,3 +1,5 @@
|
|||||||
|
//go:build cgo
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -5,430 +7,10 @@ package main
|
|||||||
*/
|
*/
|
||||||
import "C"
|
import "C"
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
bridge "core/dart-bridge"
|
bridge "core/dart-bridge"
|
||||||
"core/state"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"github.com/metacubex/mihomo/common/utils"
|
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
"sort"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/metacubex/mihomo/adapter"
|
|
||||||
"github.com/metacubex/mihomo/adapter/outboundgroup"
|
|
||||||
"github.com/metacubex/mihomo/adapter/provider"
|
|
||||||
"github.com/metacubex/mihomo/component/updater"
|
|
||||||
"github.com/metacubex/mihomo/config"
|
|
||||||
"github.com/metacubex/mihomo/constant"
|
|
||||||
cp "github.com/metacubex/mihomo/constant/provider"
|
|
||||||
"github.com/metacubex/mihomo/hub/executor"
|
|
||||||
"github.com/metacubex/mihomo/log"
|
|
||||||
"github.com/metacubex/mihomo/tunnel"
|
|
||||||
"github.com/metacubex/mihomo/tunnel/statistic"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var configParams = ConfigExtendedParams{}
|
|
||||||
|
|
||||||
var externalProviders = map[string]cp.Provider{}
|
|
||||||
|
|
||||||
var isInit = false
|
|
||||||
|
|
||||||
//export start
|
|
||||||
func start() {
|
|
||||||
runLock.Lock()
|
|
||||||
defer runLock.Unlock()
|
|
||||||
isRunning = true
|
|
||||||
}
|
|
||||||
|
|
||||||
//export stop
|
|
||||||
func stop() {
|
|
||||||
runLock.Lock()
|
|
||||||
go func() {
|
|
||||||
defer runLock.Unlock()
|
|
||||||
isRunning = false
|
|
||||||
stopListeners()
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
//export initClash
|
|
||||||
func initClash(homeDirStr *C.char) bool {
|
|
||||||
if !isInit {
|
|
||||||
constant.SetHomeDir(C.GoString(homeDirStr))
|
|
||||||
isInit = true
|
|
||||||
}
|
|
||||||
return isInit
|
|
||||||
}
|
|
||||||
|
|
||||||
//export getIsInit
|
|
||||||
func getIsInit() bool {
|
|
||||||
return isInit
|
|
||||||
}
|
|
||||||
|
|
||||||
//export restartClash
|
|
||||||
func restartClash() bool {
|
|
||||||
execPath, _ := os.Executable()
|
|
||||||
go restartExecutable(execPath)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
//export shutdownClash
|
|
||||||
func shutdownClash() bool {
|
|
||||||
stopListeners()
|
|
||||||
executor.Shutdown()
|
|
||||||
runtime.GC()
|
|
||||||
isInit = false
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
//export forceGc
|
|
||||||
func forceGc() {
|
|
||||||
go func() {
|
|
||||||
log.Infoln("[APP] request force GC")
|
|
||||||
runtime.GC()
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
//export validateConfig
|
|
||||||
func validateConfig(s *C.char, port C.longlong) {
|
|
||||||
i := int64(port)
|
|
||||||
bytes := []byte(C.GoString(s))
|
|
||||||
go func() {
|
|
||||||
_, err := config.UnmarshalRawConfig(bytes)
|
|
||||||
if err != nil {
|
|
||||||
bridge.SendToPort(i, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
bridge.SendToPort(i, "")
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
var updateLock sync.Mutex
|
|
||||||
|
|
||||||
//export updateConfig
|
|
||||||
func updateConfig(s *C.char, port C.longlong) {
|
|
||||||
i := int64(port)
|
|
||||||
paramsString := C.GoString(s)
|
|
||||||
go func() {
|
|
||||||
updateLock.Lock()
|
|
||||||
defer updateLock.Unlock()
|
|
||||||
var params = &GenerateConfigParams{}
|
|
||||||
err := json.Unmarshal([]byte(paramsString), params)
|
|
||||||
if err != nil {
|
|
||||||
bridge.SendToPort(i, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
configParams = params.Params
|
|
||||||
prof := decorationConfig(params.ProfileId, params.Config)
|
|
||||||
state.CurrentRawConfig = prof
|
|
||||||
err = applyConfig()
|
|
||||||
if err != nil {
|
|
||||||
bridge.SendToPort(i, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
bridge.SendToPort(i, "")
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
//export clearEffect
|
|
||||||
func clearEffect(s *C.char) {
|
|
||||||
id := C.GoString(s)
|
|
||||||
go func() {
|
|
||||||
_ = removeFile(getProfilePath(id))
|
|
||||||
_ = removeFile(getProfileProvidersPath(id))
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
//export getProxies
|
|
||||||
func getProxies() *C.char {
|
|
||||||
data, err := json.Marshal(tunnel.ProxiesWithProviders())
|
|
||||||
if err != nil {
|
|
||||||
return C.CString("")
|
|
||||||
}
|
|
||||||
return C.CString(string(data))
|
|
||||||
}
|
|
||||||
|
|
||||||
//export changeProxy
|
|
||||||
func changeProxy(s *C.char) {
|
|
||||||
paramsString := C.GoString(s)
|
|
||||||
var params = &ChangeProxyParams{}
|
|
||||||
err := json.Unmarshal([]byte(paramsString), params)
|
|
||||||
if err != nil {
|
|
||||||
log.Infoln("Unmarshal ChangeProxyParams %v", err)
|
|
||||||
}
|
|
||||||
groupName := *params.GroupName
|
|
||||||
proxyName := *params.ProxyName
|
|
||||||
proxies := tunnel.ProxiesWithProviders()
|
|
||||||
group, ok := proxies[groupName]
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
adapterProxy := group.(*adapter.Proxy)
|
|
||||||
selector, ok := adapterProxy.ProxyAdapter.(outboundgroup.SelectAble)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if proxyName == "" {
|
|
||||||
selector.ForceSet(proxyName)
|
|
||||||
} else {
|
|
||||||
err = selector.Set(proxyName)
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
log.Infoln("[SelectAble] %s selected %s", groupName, proxyName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//export getTraffic
|
|
||||||
func getTraffic() *C.char {
|
|
||||||
up, down := statistic.DefaultManager.Current(state.CurrentState.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 getTotalTraffic
|
|
||||||
func getTotalTraffic() *C.char {
|
|
||||||
up, down := statistic.DefaultManager.Total(state.CurrentState.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
|
|
||||||
func asyncTestDelay(s *C.char, port C.longlong) {
|
|
||||||
i := int64(port)
|
|
||||||
paramsString := C.GoString(s)
|
|
||||||
b.Go(paramsString, func() (bool, error) {
|
|
||||||
var params = &TestDelayParams{}
|
|
||||||
err := json.Unmarshal([]byte(paramsString), params)
|
|
||||||
if err != nil {
|
|
||||||
bridge.SendToPort(i, "")
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
expectedStatus, err := utils.NewUnsignedRanges[uint16]("")
|
|
||||||
if err != nil {
|
|
||||||
bridge.SendToPort(i, "")
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(params.Timeout))
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
proxies := tunnel.ProxiesWithProviders()
|
|
||||||
proxy := proxies[params.ProxyName]
|
|
||||||
|
|
||||||
delayData := &Delay{
|
|
||||||
Name: params.ProxyName,
|
|
||||||
}
|
|
||||||
|
|
||||||
if proxy == nil {
|
|
||||||
delayData.Value = -1
|
|
||||||
data, _ := json.Marshal(delayData)
|
|
||||||
bridge.SendToPort(i, string(data))
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
delay, err := proxy.URLTest(ctx, constant.DefaultTestURL, expectedStatus)
|
|
||||||
if err != nil || delay == 0 {
|
|
||||||
delayData.Value = -1
|
|
||||||
data, _ := json.Marshal(delayData)
|
|
||||||
bridge.SendToPort(i, string(data))
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
delayData.Value = int32(delay)
|
|
||||||
data, _ := json.Marshal(delayData)
|
|
||||||
bridge.SendToPort(i, string(data))
|
|
||||||
return false, nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
//export getVersionInfo
|
|
||||||
func getVersionInfo() *C.char {
|
|
||||||
versionInfo := map[string]string{
|
|
||||||
"clashName": constant.Name,
|
|
||||||
"version": "1.18.5",
|
|
||||||
}
|
|
||||||
data, err := json.Marshal(versionInfo)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("Error:", err)
|
|
||||||
return C.CString("")
|
|
||||||
}
|
|
||||||
return C.CString(string(data))
|
|
||||||
}
|
|
||||||
|
|
||||||
//export getConnections
|
|
||||||
func getConnections() *C.char {
|
|
||||||
snapshot := statistic.DefaultManager.Snapshot()
|
|
||||||
data, err := json.Marshal(snapshot)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("Error:", err)
|
|
||||||
return C.CString("")
|
|
||||||
}
|
|
||||||
return C.CString(string(data))
|
|
||||||
}
|
|
||||||
|
|
||||||
//export closeConnections
|
|
||||||
func closeConnections() {
|
|
||||||
statistic.DefaultManager.Range(func(c statistic.Tracker) bool {
|
|
||||||
err := c.Close()
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
//export closeConnection
|
|
||||||
func closeConnection(id *C.char) {
|
|
||||||
connectionId := C.GoString(id)
|
|
||||||
c := statistic.DefaultManager.Get(connectionId)
|
|
||||||
if c == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_ = c.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
//export getExternalProviders
|
|
||||||
func getExternalProviders() *C.char {
|
|
||||||
runLock.Lock()
|
|
||||||
defer runLock.Unlock()
|
|
||||||
externalProviders = getExternalProvidersRaw()
|
|
||||||
eps := make([]ExternalProvider, 0)
|
|
||||||
for _, p := range externalProviders {
|
|
||||||
externalProvider, err := toExternalProvider(p)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
eps = append(eps, *externalProvider)
|
|
||||||
}
|
|
||||||
sort.Sort(ExternalProviders(eps))
|
|
||||||
data, err := json.Marshal(eps)
|
|
||||||
if err != nil {
|
|
||||||
return C.CString("")
|
|
||||||
}
|
|
||||||
return C.CString(string(data))
|
|
||||||
}
|
|
||||||
|
|
||||||
//export getExternalProvider
|
|
||||||
func getExternalProvider(name *C.char) *C.char {
|
|
||||||
runLock.Lock()
|
|
||||||
defer runLock.Unlock()
|
|
||||||
externalProviderName := C.GoString(name)
|
|
||||||
externalProvider, exist := externalProviders[externalProviderName]
|
|
||||||
if !exist {
|
|
||||||
return C.CString("")
|
|
||||||
}
|
|
||||||
e, err := toExternalProvider(externalProvider)
|
|
||||||
if err != nil {
|
|
||||||
return C.CString("")
|
|
||||||
}
|
|
||||||
data, err := json.Marshal(e)
|
|
||||||
if err != nil {
|
|
||||||
return C.CString("")
|
|
||||||
}
|
|
||||||
return C.CString(string(data))
|
|
||||||
}
|
|
||||||
|
|
||||||
//export updateGeoData
|
|
||||||
func updateGeoData(geoType *C.char, geoName *C.char, port C.longlong) {
|
|
||||||
i := int64(port)
|
|
||||||
geoTypeString := C.GoString(geoType)
|
|
||||||
geoNameString := C.GoString(geoName)
|
|
||||||
go func() {
|
|
||||||
path := constant.Path.Resolve(geoNameString)
|
|
||||||
switch geoTypeString {
|
|
||||||
case "MMDB":
|
|
||||||
err := updater.UpdateMMDBWithPath(path)
|
|
||||||
if err != nil {
|
|
||||||
bridge.SendToPort(i, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case "ASN":
|
|
||||||
err := updater.UpdateASNWithPath(path)
|
|
||||||
if err != nil {
|
|
||||||
bridge.SendToPort(i, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case "GeoIp":
|
|
||||||
err := updater.UpdateGeoIpWithPath(path)
|
|
||||||
if err != nil {
|
|
||||||
bridge.SendToPort(i, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case "GeoSite":
|
|
||||||
err := updater.UpdateGeoSiteWithPath(path)
|
|
||||||
if err != nil {
|
|
||||||
bridge.SendToPort(i, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bridge.SendToPort(i, "")
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
//export updateExternalProvider
|
|
||||||
func updateExternalProvider(providerName *C.char, port C.longlong) {
|
|
||||||
i := int64(port)
|
|
||||||
providerNameString := C.GoString(providerName)
|
|
||||||
go func() {
|
|
||||||
externalProvider, exist := externalProviders[providerNameString]
|
|
||||||
if !exist {
|
|
||||||
bridge.SendToPort(i, "external provider is not exist")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err := externalProvider.Update()
|
|
||||||
if err != nil {
|
|
||||||
bridge.SendToPort(i, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
bridge.SendToPort(i, "")
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
//export sideLoadExternalProvider
|
|
||||||
func sideLoadExternalProvider(providerName *C.char, data *C.char, port C.longlong) {
|
|
||||||
i := int64(port)
|
|
||||||
bytes := []byte(C.GoString(data))
|
|
||||||
providerNameString := C.GoString(providerName)
|
|
||||||
go func() {
|
|
||||||
externalProvider, exist := externalProviders[providerNameString]
|
|
||||||
if !exist {
|
|
||||||
bridge.SendToPort(i, "external provider is not exist")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err := sideUpdateExternalProvider(externalProvider, bytes)
|
|
||||||
if err != nil {
|
|
||||||
bridge.SendToPort(i, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
bridge.SendToPort(i, "")
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
//export initNativeApiBridge
|
//export initNativeApiBridge
|
||||||
func initNativeApiBridge(api unsafe.Pointer) {
|
func initNativeApiBridge(api unsafe.Pointer) {
|
||||||
bridge.InitDartApi(api)
|
bridge.InitDartApi(api)
|
||||||
@@ -445,31 +27,156 @@ func freeCString(s *C.char) {
|
|||||||
C.free(unsafe.Pointer(s))
|
C.free(unsafe.Pointer(s))
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
//export initClash
|
||||||
provider.HealthcheckHook = func(name string, delay uint16) {
|
func initClash(homeDirStr *C.char) bool {
|
||||||
delayData := &Delay{
|
return handleInitClash(C.GoString(homeDirStr))
|
||||||
Name: name,
|
|
||||||
}
|
}
|
||||||
if delay == 0 {
|
|
||||||
delayData.Value = -1
|
//export startListener
|
||||||
} else {
|
func startListener() {
|
||||||
delayData.Value = int32(delay)
|
handleStartListener()
|
||||||
}
|
}
|
||||||
SendMessage(Message{
|
|
||||||
Type: DelayMessage,
|
//export stopListener
|
||||||
Data: delayData,
|
func stopListener() {
|
||||||
|
handleStopListener()
|
||||||
|
}
|
||||||
|
|
||||||
|
//export getIsInit
|
||||||
|
func getIsInit() bool {
|
||||||
|
return handleGetIsInit()
|
||||||
|
}
|
||||||
|
|
||||||
|
//export shutdownClash
|
||||||
|
func shutdownClash() bool {
|
||||||
|
return handleShutdown()
|
||||||
|
}
|
||||||
|
|
||||||
|
//export forceGc
|
||||||
|
func forceGc() {
|
||||||
|
handleForceGc()
|
||||||
|
}
|
||||||
|
|
||||||
|
//export validateConfig
|
||||||
|
func validateConfig(s *C.char, port C.longlong) {
|
||||||
|
i := int64(port)
|
||||||
|
bytes := []byte(C.GoString(s))
|
||||||
|
go func() {
|
||||||
|
bridge.SendToPort(i, handleValidateConfig(bytes))
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
//export updateConfig
|
||||||
|
func updateConfig(s *C.char, port C.longlong) {
|
||||||
|
i := int64(port)
|
||||||
|
bytes := []byte(C.GoString(s))
|
||||||
|
go func() {
|
||||||
|
bridge.SendToPort(i, handleUpdateConfig(bytes))
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
//export getProxies
|
||||||
|
func getProxies() *C.char {
|
||||||
|
return C.CString(handleGetProxies())
|
||||||
|
}
|
||||||
|
|
||||||
|
//export changeProxy
|
||||||
|
func changeProxy(s *C.char, port C.longlong) {
|
||||||
|
i := int64(port)
|
||||||
|
paramsString := C.GoString(s)
|
||||||
|
handleChangeProxy(paramsString, func(value string) {
|
||||||
|
bridge.SendToPort(i, value)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
statistic.DefaultRequestNotify = func(c statistic.Tracker) {
|
|
||||||
SendMessage(Message{
|
//export getTraffic
|
||||||
Type: RequestMessage,
|
func getTraffic(port C.int) *C.char {
|
||||||
Data: c,
|
onlyProxy := int(port) == 1
|
||||||
|
return C.CString(handleGetTraffic(onlyProxy))
|
||||||
|
}
|
||||||
|
|
||||||
|
//export getTotalTraffic
|
||||||
|
func getTotalTraffic(port C.int) *C.char {
|
||||||
|
onlyProxy := int(port) == 1
|
||||||
|
return C.CString(handleGetTotalTraffic(onlyProxy))
|
||||||
|
}
|
||||||
|
|
||||||
|
//export resetTraffic
|
||||||
|
func resetTraffic() {
|
||||||
|
handleResetTraffic()
|
||||||
|
}
|
||||||
|
|
||||||
|
//export asyncTestDelay
|
||||||
|
func asyncTestDelay(s *C.char, port C.longlong) {
|
||||||
|
i := int64(port)
|
||||||
|
paramsString := C.GoString(s)
|
||||||
|
handleAsyncTestDelay(paramsString, func(value string) {
|
||||||
|
bridge.SendToPort(i, value)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
executor.DefaultProviderLoadedHook = func(providerName string) {
|
|
||||||
SendMessage(Message{
|
//export getConnections
|
||||||
Type: LoadedMessage,
|
func getConnections() *C.char {
|
||||||
Data: providerName,
|
return C.CString(handleGetConnections())
|
||||||
|
}
|
||||||
|
|
||||||
|
//export closeConnections
|
||||||
|
func closeConnections() {
|
||||||
|
handleCloseConnections()
|
||||||
|
}
|
||||||
|
|
||||||
|
//export closeConnection
|
||||||
|
func closeConnection(id *C.char) {
|
||||||
|
connectionId := C.GoString(id)
|
||||||
|
handleCloseConnection(connectionId)
|
||||||
|
}
|
||||||
|
|
||||||
|
//export getExternalProviders
|
||||||
|
func getExternalProviders() *C.char {
|
||||||
|
return C.CString(handleGetExternalProviders())
|
||||||
|
}
|
||||||
|
|
||||||
|
//export getExternalProvider
|
||||||
|
func getExternalProvider(externalProviderNameChar *C.char) *C.char {
|
||||||
|
externalProviderName := C.GoString(externalProviderNameChar)
|
||||||
|
return C.CString(handleGetExternalProvider(externalProviderName))
|
||||||
|
}
|
||||||
|
|
||||||
|
//export updateGeoData
|
||||||
|
func updateGeoData(geoTypeChar *C.char, geoNameChar *C.char, port C.longlong) {
|
||||||
|
i := int64(port)
|
||||||
|
geoType := C.GoString(geoTypeChar)
|
||||||
|
geoName := C.GoString(geoNameChar)
|
||||||
|
handleUpdateGeoData(geoType, geoName, func(value string) {
|
||||||
|
bridge.SendToPort(i, value)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//export updateExternalProvider
|
||||||
|
func updateExternalProvider(providerNameChar *C.char, port C.longlong) {
|
||||||
|
i := int64(port)
|
||||||
|
providerName := C.GoString(providerNameChar)
|
||||||
|
handleUpdateExternalProvider(providerName, func(value string) {
|
||||||
|
bridge.SendToPort(i, value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//export sideLoadExternalProvider
|
||||||
|
func sideLoadExternalProvider(providerNameChar *C.char, dataChar *C.char, port C.longlong) {
|
||||||
|
i := int64(port)
|
||||||
|
providerName := C.GoString(providerNameChar)
|
||||||
|
data := []byte(C.GoString(dataChar))
|
||||||
|
handleSideLoadExternalProvider(providerName, data, func(value string) {
|
||||||
|
bridge.SendToPort(i, value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//export startLog
|
||||||
|
func startLog() {
|
||||||
|
handleStartLog()
|
||||||
|
}
|
||||||
|
|
||||||
|
//export stopLog
|
||||||
|
func stopLog() {
|
||||||
|
handleStopLog()
|
||||||
}
|
}
|
||||||
|
|||||||
270
core/lib_android.go
Normal file
270
core/lib_android.go
Normal file
@@ -0,0 +1,270 @@
|
|||||||
|
//go:build android && cgo
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "C"
|
||||||
|
import (
|
||||||
|
"core/platform"
|
||||||
|
"core/state"
|
||||||
|
t "core/tun"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/metacubex/mihomo/component/dialer"
|
||||||
|
"github.com/metacubex/mihomo/component/process"
|
||||||
|
"github.com/metacubex/mihomo/constant"
|
||||||
|
"github.com/metacubex/mihomo/dns"
|
||||||
|
"github.com/metacubex/mihomo/listener/sing_tun"
|
||||||
|
"github.com/metacubex/mihomo/log"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ProcessMap struct {
|
||||||
|
m sync.Map
|
||||||
|
}
|
||||||
|
|
||||||
|
type FdMap struct {
|
||||||
|
m sync.Map
|
||||||
|
}
|
||||||
|
|
||||||
|
type Fd struct {
|
||||||
|
Id int64 `json:"id"`
|
||||||
|
Value int64 `json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
tunListener *sing_tun.Listener
|
||||||
|
fdMap FdMap
|
||||||
|
fdCounter int64 = 0
|
||||||
|
counter int64 = 0
|
||||||
|
processMap ProcessMap
|
||||||
|
tunLock sync.Mutex
|
||||||
|
runTime *time.Time
|
||||||
|
errBlocked = errors.New("blocked")
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
//export startTUN
|
||||||
|
func startTUN(fd C.int, port C.longlong) {
|
||||||
|
i := int64(port)
|
||||||
|
ServicePort = i
|
||||||
|
if fd == 0 {
|
||||||
|
tunLock.Lock()
|
||||||
|
defer tunLock.Unlock()
|
||||||
|
now := time.Now()
|
||||||
|
runTime = &now
|
||||||
|
SendMessage(Message{
|
||||||
|
Type: StartedMessage,
|
||||||
|
Data: strconv.FormatInt(runTime.UnixMilli(), 10),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
initSocketHook()
|
||||||
|
go func() {
|
||||||
|
tunLock.Lock()
|
||||||
|
defer tunLock.Unlock()
|
||||||
|
f := int(fd)
|
||||||
|
tunListener, _ = t.Start(f, currentConfig.General.Tun.Device, currentConfig.General.Tun.Stack)
|
||||||
|
if tunListener != nil {
|
||||||
|
log.Infoln("TUN address: %v", tunListener.Address())
|
||||||
|
}
|
||||||
|
now := time.Now()
|
||||||
|
runTime = &now
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
//export getRunTime
|
||||||
|
func getRunTime() *C.char {
|
||||||
|
if runTime == nil {
|
||||||
|
return C.CString("")
|
||||||
|
}
|
||||||
|
return C.CString(strconv.FormatInt(runTime.UnixMilli(), 10))
|
||||||
|
}
|
||||||
|
|
||||||
|
//export stopTun
|
||||||
|
func stopTun() {
|
||||||
|
removeSocketHook()
|
||||||
|
go func() {
|
||||||
|
tunLock.Lock()
|
||||||
|
defer tunLock.Unlock()
|
||||||
|
|
||||||
|
runTime = nil
|
||||||
|
|
||||||
|
if tunListener != nil {
|
||||||
|
_ = tunListener.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
//export setFdMap
|
||||||
|
func setFdMap(fd C.long) {
|
||||||
|
fdInt := int64(fd)
|
||||||
|
go func() {
|
||||||
|
fdMap.Store(fdInt)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func markSocket(fd Fd) {
|
||||||
|
SendMessage(Message{
|
||||||
|
Type: ProtectMessage,
|
||||||
|
Data: fd,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func initSocketHook() {
|
||||||
|
dialer.DefaultSocketHook = func(network, address string, conn syscall.RawConn) error {
|
||||||
|
if platform.ShouldBlockConnection() {
|
||||||
|
return errBlocked
|
||||||
|
}
|
||||||
|
return conn.Control(func(fd uintptr) {
|
||||||
|
fdInt := int64(fd)
|
||||||
|
timeout := time.After(500 * 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(20 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeSocketHook() {
|
||||||
|
dialer.DefaultSocketHook = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
//export getCurrentProfileName
|
||||||
|
func getCurrentProfileName() *C.char {
|
||||||
|
if state.CurrentState == nil {
|
||||||
|
return C.CString("")
|
||||||
|
}
|
||||||
|
return C.CString(state.CurrentState.CurrentProfileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
//export getAndroidVpnOptions
|
||||||
|
func getAndroidVpnOptions() *C.char {
|
||||||
|
tunLock.Lock()
|
||||||
|
defer tunLock.Unlock()
|
||||||
|
options := state.AndroidVpnOptions{
|
||||||
|
Enable: state.CurrentState.Enable,
|
||||||
|
Port: currentConfig.General.MixedPort,
|
||||||
|
Ipv4Address: state.DefaultIpv4Address,
|
||||||
|
Ipv6Address: state.GetIpv6Address(),
|
||||||
|
AccessControl: state.CurrentState.AccessControl,
|
||||||
|
SystemProxy: state.CurrentState.SystemProxy,
|
||||||
|
AllowBypass: state.CurrentState.AllowBypass,
|
||||||
|
RouteAddress: state.CurrentState.RouteAddress,
|
||||||
|
BypassDomain: state.CurrentState.BypassDomain,
|
||||||
|
DnsServerAddress: state.GetDnsServerAddress(),
|
||||||
|
}
|
||||||
|
data, err := json.Marshal(options)
|
||||||
|
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.CurrentState)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//export updateDns
|
||||||
|
func updateDns(s *C.char) {
|
||||||
|
dnsList := C.GoString(s)
|
||||||
|
go func() {
|
||||||
|
log.Infoln("[DNS] updateDns %s", dnsList)
|
||||||
|
dns.UpdateSystemDNS(strings.Split(dnsList, ","))
|
||||||
|
dns.FlushCacheWithDefaultResolver()
|
||||||
|
}()
|
||||||
|
}
|
||||||
38
core/log.go
38
core/log.go
@@ -1,38 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import "C"
|
|
||||||
import (
|
|
||||||
"github.com/metacubex/mihomo/common/observable"
|
|
||||||
"github.com/metacubex/mihomo/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
var logSubscriber observable.Subscription[log.Event]
|
|
||||||
|
|
||||||
//export startLog
|
|
||||||
func startLog() {
|
|
||||||
if logSubscriber != nil {
|
|
||||||
log.UnSubscribe(logSubscriber)
|
|
||||||
logSubscriber = nil
|
|
||||||
}
|
|
||||||
logSubscriber = log.Subscribe()
|
|
||||||
go func() {
|
|
||||||
for logData := range logSubscriber {
|
|
||||||
if logData.LogLevel < log.Level() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
message := &Message{
|
|
||||||
Type: LogMessage,
|
|
||||||
Data: logData,
|
|
||||||
}
|
|
||||||
SendMessage(*message)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
//export stopLog
|
|
||||||
func stopLog() {
|
|
||||||
if logSubscriber != nil {
|
|
||||||
log.UnSubscribe(logSubscriber)
|
|
||||||
logSubscriber = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
11
core/main.go
11
core/main.go
@@ -1,10 +1,17 @@
|
|||||||
|
//go:build !cgo
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import "C"
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
fmt.Println("init clash")
|
args := os.Args
|
||||||
|
if len(args) <= 1 {
|
||||||
|
fmt.Println("Arguments error")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
startServer(args[1])
|
||||||
}
|
}
|
||||||
|
|||||||
8
core/main_cgo.go
Normal file
8
core/main_cgo.go
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
//go:build cgo
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
}
|
||||||
@@ -1,77 +1,13 @@
|
|||||||
|
//go:build !cgo
|
||||||
|
|
||||||
package main
|
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) {
|
func SendMessage(message Message) {
|
||||||
s, err := message.Json()
|
s, err := message.Json()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if handler, ok := messageHandlers[message.Type]; ok {
|
Action{
|
||||||
handler(s)
|
Method: messageMethod,
|
||||||
} else {
|
}.callback(s)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|||||||
47
core/message_cgo.go
Normal file
47
core/message_cgo.go
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
//go:build cgo
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
bridge "core/dart-bridge"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
Port int64 = -1
|
||||||
|
ServicePort int64 = -1
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
//go:build android
|
//go:build android && cgo
|
||||||
|
|
||||||
package platform
|
package platform
|
||||||
|
|
||||||
|
|||||||
@@ -1,81 +0,0 @@
|
|||||||
//go:build android
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
162
core/server.go
Normal file
162
core/server.go
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
//go:build !cgo
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
var conn net.Conn = nil
|
||||||
|
|
||||||
|
func startServer(arg string) {
|
||||||
|
_, err := strconv.Atoi(arg)
|
||||||
|
if err != nil {
|
||||||
|
conn, err = net.Dial("unix", arg)
|
||||||
|
} else {
|
||||||
|
conn, err = net.Dial("tcp", fmt.Sprintf("127.0.0.1:%s", arg))
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
panic(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func(conn net.Conn) {
|
||||||
|
_ = conn.Close()
|
||||||
|
}(conn)
|
||||||
|
|
||||||
|
reader := bufio.NewReader(conn)
|
||||||
|
|
||||||
|
for {
|
||||||
|
data, err := reader.ReadString('\n')
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var action = &Action{}
|
||||||
|
|
||||||
|
err = json.Unmarshal([]byte(data), action)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
go handleAction(action)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleAction(action *Action) {
|
||||||
|
switch action.Method {
|
||||||
|
case initClashMethod:
|
||||||
|
data := action.Data.(string)
|
||||||
|
action.callback(handleInitClash(data))
|
||||||
|
return
|
||||||
|
case getIsInitMethod:
|
||||||
|
action.callback(handleGetIsInit())
|
||||||
|
return
|
||||||
|
case forceGcMethod:
|
||||||
|
handleForceGc()
|
||||||
|
return
|
||||||
|
case shutdownMethod:
|
||||||
|
action.callback(handleShutdown())
|
||||||
|
return
|
||||||
|
case validateConfigMethod:
|
||||||
|
data := []byte(action.Data.(string))
|
||||||
|
action.callback(handleValidateConfig(data))
|
||||||
|
return
|
||||||
|
case updateConfigMethod:
|
||||||
|
data := []byte(action.Data.(string))
|
||||||
|
action.callback(handleUpdateConfig(data))
|
||||||
|
return
|
||||||
|
case getProxiesMethod:
|
||||||
|
action.callback(handleGetProxies())
|
||||||
|
return
|
||||||
|
case changeProxyMethod:
|
||||||
|
data := action.Data.(string)
|
||||||
|
handleChangeProxy(data, func(value string) {
|
||||||
|
action.callback(value)
|
||||||
|
})
|
||||||
|
return
|
||||||
|
case getTrafficMethod:
|
||||||
|
data := action.Data.(bool)
|
||||||
|
action.callback(handleGetTraffic(data))
|
||||||
|
return
|
||||||
|
case getTotalTrafficMethod:
|
||||||
|
data := action.Data.(bool)
|
||||||
|
action.callback(handleGetTotalTraffic(data))
|
||||||
|
return
|
||||||
|
case resetTrafficMethod:
|
||||||
|
handleResetTraffic()
|
||||||
|
return
|
||||||
|
case asyncTestDelayMethod:
|
||||||
|
data := action.Data.(string)
|
||||||
|
handleAsyncTestDelay(data, func(value string) {
|
||||||
|
action.callback(value)
|
||||||
|
})
|
||||||
|
return
|
||||||
|
case getConnectionsMethod:
|
||||||
|
action.callback(handleGetConnections())
|
||||||
|
return
|
||||||
|
case closeConnectionsMethod:
|
||||||
|
action.callback(handleCloseConnections())
|
||||||
|
return
|
||||||
|
case closeConnectionMethod:
|
||||||
|
id := action.Data.(string)
|
||||||
|
action.callback(handleCloseConnection(id))
|
||||||
|
return
|
||||||
|
case getExternalProvidersMethod:
|
||||||
|
action.callback(handleGetExternalProviders())
|
||||||
|
return
|
||||||
|
case getExternalProviderMethod:
|
||||||
|
externalProviderName := action.Data.(string)
|
||||||
|
action.callback(handleGetExternalProvider(externalProviderName))
|
||||||
|
case updateGeoDataMethod:
|
||||||
|
paramsString := action.Data.(string)
|
||||||
|
var params = map[string]string{}
|
||||||
|
err := json.Unmarshal([]byte(paramsString), ¶ms)
|
||||||
|
if err != nil {
|
||||||
|
action.callback(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
geoType := params["geoType"]
|
||||||
|
geoName := params["geoName"]
|
||||||
|
handleUpdateGeoData(geoType, geoName, func(value string) {
|
||||||
|
action.callback(value)
|
||||||
|
})
|
||||||
|
return
|
||||||
|
case updateExternalProviderMethod:
|
||||||
|
providerName := action.Data.(string)
|
||||||
|
handleUpdateExternalProvider(providerName, func(value string) {
|
||||||
|
action.callback(value)
|
||||||
|
})
|
||||||
|
return
|
||||||
|
case sideLoadExternalProviderMethod:
|
||||||
|
paramsString := action.Data.(string)
|
||||||
|
var params = map[string]string{}
|
||||||
|
err := json.Unmarshal([]byte(paramsString), ¶ms)
|
||||||
|
if err != nil {
|
||||||
|
action.callback(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
providerName := params["providerName"]
|
||||||
|
data := params["data"]
|
||||||
|
handleSideLoadExternalProvider(providerName, []byte(data), func(value string) {
|
||||||
|
action.callback(value)
|
||||||
|
})
|
||||||
|
return
|
||||||
|
case startLogMethod:
|
||||||
|
handleStartLog()
|
||||||
|
return
|
||||||
|
case stopLogMethod:
|
||||||
|
handleStopLog()
|
||||||
|
return
|
||||||
|
case startListenerMethod:
|
||||||
|
action.callback(handleStartListener())
|
||||||
|
return
|
||||||
|
case stopListenerMethod:
|
||||||
|
action.callback(handleStopListener())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import "C"
|
|
||||||
import (
|
|
||||||
"core/state"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
//export getCurrentProfileName
|
|
||||||
func getCurrentProfileName() *C.char {
|
|
||||||
if state.CurrentState == nil {
|
|
||||||
return C.CString("")
|
|
||||||
}
|
|
||||||
return C.CString(state.CurrentState.CurrentProfileName)
|
|
||||||
}
|
|
||||||
|
|
||||||
//export getAndroidVpnOptions
|
|
||||||
func getAndroidVpnOptions() *C.char {
|
|
||||||
options := state.AndroidVpnOptions{
|
|
||||||
Enable: state.CurrentState.Enable,
|
|
||||||
Port: state.CurrentRawConfig.MixedPort,
|
|
||||||
Ipv4Address: state.DefaultIpv4Address,
|
|
||||||
Ipv6Address: state.GetIpv6Address(),
|
|
||||||
AccessControl: state.CurrentState.AccessControl,
|
|
||||||
SystemProxy: state.CurrentState.SystemProxy,
|
|
||||||
AllowBypass: state.CurrentState.AllowBypass,
|
|
||||||
RouteAddress: state.CurrentState.RouteAddress,
|
|
||||||
BypassDomain: state.CurrentState.BypassDomain,
|
|
||||||
DnsServerAddress: state.GetDnsServerAddress(),
|
|
||||||
}
|
|
||||||
data, err := json.Marshal(options)
|
|
||||||
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.CurrentState)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +1,11 @@
|
|||||||
package state
|
//go:build android && cgo
|
||||||
|
|
||||||
import "github.com/metacubex/mihomo/config"
|
package state
|
||||||
|
|
||||||
var DefaultIpv4Address = "172.19.0.1/30"
|
var DefaultIpv4Address = "172.19.0.1/30"
|
||||||
var DefaultDnsAddress = "172.19.0.2"
|
var DefaultDnsAddress = "172.19.0.2"
|
||||||
var DefaultIpv6Address = "fdfe:dcba:9876::1/126"
|
var DefaultIpv6Address = "fdfe:dcba:9876::1/126"
|
||||||
|
|
||||||
var CurrentRawConfig = config.DefaultRawConfig()
|
|
||||||
|
|
||||||
type AndroidVpnOptions struct {
|
type AndroidVpnOptions struct {
|
||||||
Enable bool `json:"enable"`
|
Enable bool `json:"enable"`
|
||||||
Port int `json:"port"`
|
Port int `json:"port"`
|
||||||
@@ -41,7 +39,6 @@ type AndroidVpnRawOptions struct {
|
|||||||
type State struct {
|
type State struct {
|
||||||
AndroidVpnRawOptions
|
AndroidVpnRawOptions
|
||||||
CurrentProfileName string `json:"currentProfileName"`
|
CurrentProfileName string `json:"currentProfileName"`
|
||||||
OnlyProxy bool `json:"onlyProxy"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var CurrentState = &State{}
|
var CurrentState = &State{}
|
||||||
@@ -55,7 +52,5 @@ func GetIpv6Address() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetDnsServerAddress() string {
|
func GetDnsServerAddress() string {
|
||||||
//prefix, _ := netip.ParsePrefix(DefaultIpv4Address)
|
|
||||||
//return prefix.Addr().String()
|
|
||||||
return DefaultDnsAddress
|
return DefaultDnsAddress
|
||||||
}
|
}
|
||||||
|
|||||||
157
core/tun.go
157
core/tun.go
@@ -1,157 +0,0 @@
|
|||||||
//go:build android
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import "C"
|
|
||||||
import (
|
|
||||||
"core/platform"
|
|
||||||
t "core/tun"
|
|
||||||
"errors"
|
|
||||||
"github.com/metacubex/mihomo/listener/sing_tun"
|
|
||||||
"strconv"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/metacubex/mihomo/component/dialer"
|
|
||||||
"github.com/metacubex/mihomo/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
var tunLock sync.Mutex
|
|
||||||
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 (
|
|
||||||
tunListener *sing_tun.Listener
|
|
||||||
fdMap FdMap
|
|
||||||
fdCounter int64 = 0
|
|
||||||
)
|
|
||||||
|
|
||||||
//export startTUN
|
|
||||||
func startTUN(fd C.int, port C.longlong) {
|
|
||||||
i := int64(port)
|
|
||||||
ServicePort = i
|
|
||||||
if fd == 0 {
|
|
||||||
tunLock.Lock()
|
|
||||||
defer tunLock.Unlock()
|
|
||||||
now := time.Now()
|
|
||||||
runTime = &now
|
|
||||||
SendMessage(Message{
|
|
||||||
Type: StartedMessage,
|
|
||||||
Data: strconv.FormatInt(runTime.UnixMilli(), 10),
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
initSocketHook()
|
|
||||||
go func() {
|
|
||||||
tunLock.Lock()
|
|
||||||
defer tunLock.Unlock()
|
|
||||||
f := int(fd)
|
|
||||||
tunListener, _ = t.Start(f)
|
|
||||||
if tunListener != nil {
|
|
||||||
log.Infoln("TUN address: %v", tunListener.Address())
|
|
||||||
}
|
|
||||||
|
|
||||||
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() {
|
|
||||||
removeSocketHook()
|
|
||||||
go func() {
|
|
||||||
tunLock.Lock()
|
|
||||||
defer tunLock.Unlock()
|
|
||||||
|
|
||||||
runTime = nil
|
|
||||||
|
|
||||||
if tunListener != nil {
|
|
||||||
_ = tunListener.Close()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func initSocketHook() {
|
|
||||||
dialer.DefaultSocketHook = func(network, address string, conn syscall.RawConn) error {
|
|
||||||
if platform.ShouldBlockConnection() {
|
|
||||||
return errBlocked
|
|
||||||
}
|
|
||||||
return conn.Control(func(fd uintptr) {
|
|
||||||
fdInt := int64(fd)
|
|
||||||
timeout := time.After(500 * 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(20 * time.Millisecond)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func removeSocketHook() {
|
|
||||||
dialer.DefaultSocketHook = nil
|
|
||||||
}
|
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
//go:build android
|
//go:build android && cgo
|
||||||
|
|
||||||
package tun
|
package tun
|
||||||
|
|
||||||
import "C"
|
import "C"
|
||||||
import (
|
import (
|
||||||
"core/state"
|
"core/state"
|
||||||
|
"github.com/metacubex/mihomo/constant"
|
||||||
LC "github.com/metacubex/mihomo/listener/config"
|
LC "github.com/metacubex/mihomo/listener/config"
|
||||||
"github.com/metacubex/mihomo/listener/sing_tun"
|
"github.com/metacubex/mihomo/listener/sing_tun"
|
||||||
"github.com/metacubex/mihomo/log"
|
"github.com/metacubex/mihomo/log"
|
||||||
@@ -23,7 +24,7 @@ type Props struct {
|
|||||||
Dns6 string `json:"dns6"`
|
Dns6 string `json:"dns6"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func Start(fd int) (*sing_tun.Listener, error) {
|
func Start(fd int, device string, stack constant.TUNStack) (*sing_tun.Listener, error) {
|
||||||
var prefix4 []netip.Prefix
|
var prefix4 []netip.Prefix
|
||||||
tempPrefix4, err := netip.ParsePrefix(state.DefaultIpv4Address)
|
tempPrefix4, err := netip.ParsePrefix(state.DefaultIpv4Address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -46,8 +47,8 @@ func Start(fd int) (*sing_tun.Listener, error) {
|
|||||||
|
|
||||||
options := LC.Tun{
|
options := LC.Tun{
|
||||||
Enable: true,
|
Enable: true,
|
||||||
Device: state.CurrentRawConfig.Tun.Device,
|
Device: device,
|
||||||
Stack: state.CurrentRawConfig.Tun.Stack,
|
Stack: stack,
|
||||||
DNSHijack: dnsHijack,
|
DNSHijack: dnsHijack,
|
||||||
AutoRoute: false,
|
AutoRoute: false,
|
||||||
AutoDetectInterface: false,
|
AutoDetectInterface: false,
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:animations/animations.dart';
|
import 'package:animations/animations.dart';
|
||||||
import 'package:dynamic_color/dynamic_color.dart';
|
import 'package:dynamic_color/dynamic_color.dart';
|
||||||
import 'package:fl_clash/l10n/l10n.dart';
|
import 'package:fl_clash/clash/clash.dart';
|
||||||
import 'package:fl_clash/common/common.dart';
|
import 'package:fl_clash/common/common.dart';
|
||||||
|
import 'package:fl_clash/l10n/l10n.dart';
|
||||||
import 'package:fl_clash/manager/hotkey_manager.dart';
|
import 'package:fl_clash/manager/hotkey_manager.dart';
|
||||||
import 'package:fl_clash/manager/manager.dart';
|
import 'package:fl_clash/manager/manager.dart';
|
||||||
import 'package:fl_clash/plugins/app.dart';
|
import 'package:fl_clash/plugins/app.dart';
|
||||||
@@ -245,8 +247,10 @@ class ApplicationState extends State<Application> {
|
|||||||
@override
|
@override
|
||||||
Future<void> dispose() async {
|
Future<void> dispose() async {
|
||||||
linkManager.destroy();
|
linkManager.destroy();
|
||||||
await globalState.appController.savePreferences();
|
|
||||||
super.dispose();
|
|
||||||
_cancelTimer();
|
_cancelTimer();
|
||||||
|
await clashService?.destroy();
|
||||||
|
await globalState.appController.savePreferences();
|
||||||
|
await globalState.appController.handleExit();
|
||||||
|
super.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
export 'core.dart';
|
export 'core.dart';
|
||||||
export 'service.dart';
|
export 'lib.dart';
|
||||||
export 'message.dart';
|
export 'message.dart';
|
||||||
|
export 'service.dart';
|
||||||
|
|||||||
@@ -1,42 +1,26 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:ffi';
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:isolate';
|
import 'dart:isolate';
|
||||||
|
|
||||||
import 'package:ffi/ffi.dart';
|
import 'package:fl_clash/clash/clash.dart';
|
||||||
|
import 'package:fl_clash/clash/interface.dart';
|
||||||
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';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'generated/clash_ffi.dart';
|
import 'package:path/path.dart';
|
||||||
|
|
||||||
class ClashCore {
|
class ClashCore {
|
||||||
static ClashCore? _instance;
|
static ClashCore? _instance;
|
||||||
static final receiver = ReceivePort();
|
late ClashInterface clashInterface;
|
||||||
|
|
||||||
late final ClashFFI clashFFI;
|
|
||||||
late final DynamicLibrary lib;
|
|
||||||
|
|
||||||
DynamicLibrary _getClashLib() {
|
|
||||||
if (Platform.isWindows) {
|
|
||||||
return DynamicLibrary.open("libclash.dll");
|
|
||||||
}
|
|
||||||
if (Platform.isMacOS) {
|
|
||||||
return DynamicLibrary.open("libclash.dylib");
|
|
||||||
}
|
|
||||||
if (Platform.isAndroid || Platform.isLinux) {
|
|
||||||
return DynamicLibrary.open("libclash.so");
|
|
||||||
}
|
|
||||||
throw "Platform is not supported";
|
|
||||||
}
|
|
||||||
|
|
||||||
ClashCore._internal() {
|
ClashCore._internal() {
|
||||||
lib = _getClashLib();
|
if (Platform.isAndroid) {
|
||||||
clashFFI = ClashFFI(lib);
|
clashInterface = clashLib!;
|
||||||
clashFFI.initNativeApiBridge(
|
} else {
|
||||||
NativeApi.initializeApiDLData,
|
clashInterface = clashService!;
|
||||||
);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
factory ClashCore() {
|
factory ClashCore() {
|
||||||
@@ -44,67 +28,62 @@ class ClashCore {
|
|||||||
return _instance!;
|
return _instance!;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool init(String homeDir) {
|
Future<void> _initGeo() async {
|
||||||
final homeDirChar = homeDir.toNativeUtf8().cast<Char>();
|
final homePath = await appPath.getHomeDirPath();
|
||||||
final isInit = clashFFI.initClash(homeDirChar) == 1;
|
final homeDir = Directory(homePath);
|
||||||
malloc.free(homeDirChar);
|
final isExists = await homeDir.exists();
|
||||||
return isInit;
|
if (!isExists) {
|
||||||
|
await homeDir.create(recursive: true);
|
||||||
}
|
}
|
||||||
|
const geoFileNameList = [
|
||||||
shutdown() {
|
mmdbFileName,
|
||||||
clashFFI.shutdownClash();
|
geoIpFileName,
|
||||||
lib.close();
|
geoSiteFileName,
|
||||||
}
|
asnFileName,
|
||||||
|
];
|
||||||
bool get isInit => clashFFI.getIsInit() == 1;
|
try {
|
||||||
|
for (final geoFileName in geoFileNameList) {
|
||||||
Future<String> validateConfig(String data) {
|
final geoFile = File(
|
||||||
final completer = Completer<String>();
|
join(homePath, geoFileName),
|
||||||
final receiver = ReceivePort();
|
|
||||||
receiver.listen((message) {
|
|
||||||
if (!completer.isCompleted) {
|
|
||||||
completer.complete(message);
|
|
||||||
receiver.close();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
final dataChar = data.toNativeUtf8().cast<Char>();
|
|
||||||
clashFFI.validateConfig(
|
|
||||||
dataChar,
|
|
||||||
receiver.sendPort.nativePort,
|
|
||||||
);
|
);
|
||||||
malloc.free(dataChar);
|
final isExists = await geoFile.exists();
|
||||||
return completer.future;
|
if (isExists) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final data = await rootBundle.load('assets/data/$geoFileName');
|
||||||
|
List<int> bytes = data.buffer.asUint8List();
|
||||||
|
await geoFile.writeAsBytes(bytes, flush: true);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String> updateConfig(UpdateConfigParams updateConfigParams) {
|
Future<bool> init({
|
||||||
final completer = Completer<String>();
|
required ClashConfig clashConfig,
|
||||||
final receiver = ReceivePort();
|
required Config config,
|
||||||
receiver.listen((message) {
|
}) async {
|
||||||
if (!completer.isCompleted) {
|
await _initGeo();
|
||||||
completer.complete(message);
|
final homeDirPath = await appPath.getHomeDirPath();
|
||||||
receiver.close();
|
return await clashInterface.init(homeDirPath);
|
||||||
}
|
|
||||||
});
|
|
||||||
final params = json.encode(updateConfigParams);
|
|
||||||
final paramsChar = params.toNativeUtf8().cast<Char>();
|
|
||||||
clashFFI.updateConfig(
|
|
||||||
paramsChar,
|
|
||||||
receiver.sendPort.nativePort,
|
|
||||||
);
|
|
||||||
malloc.free(paramsChar);
|
|
||||||
return completer.future;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
initMessage() {
|
shutdown() async {
|
||||||
clashFFI.initMessage(
|
await clashInterface.shutdown();
|
||||||
receiver.sendPort.nativePort,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<Group>> getProxiesGroups() {
|
FutureOr<bool> get isInit => clashInterface.isInit;
|
||||||
final proxiesRaw = clashFFI.getProxies();
|
|
||||||
final proxiesRawString = proxiesRaw.cast<Utf8>().toDartString();
|
FutureOr<String> validateConfig(String data) {
|
||||||
clashFFI.freeCString(proxiesRaw);
|
return clashInterface.validateConfig(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> updateConfig(UpdateConfigParams updateConfigParams) async {
|
||||||
|
return await clashInterface.updateConfig(updateConfigParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<Group>> getProxiesGroups() async {
|
||||||
|
final proxiesRawString = await clashInterface.getProxies();
|
||||||
return Isolate.run<List<Group>>(() {
|
return Isolate.run<List<Group>>(() {
|
||||||
if (proxiesRawString.isEmpty) return [];
|
if (proxiesRawString.isEmpty) return [];
|
||||||
final proxies = (json.decode(proxiesRawString) ?? {}) as Map;
|
final proxies = (json.decode(proxiesRawString) ?? {}) as Map;
|
||||||
@@ -134,12 +113,30 @@ class ClashCore {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<ExternalProvider>> getExternalProviders() {
|
FutureOr<String> changeProxy(ChangeProxyParams changeProxyParams) async {
|
||||||
final externalProvidersRaw = clashFFI.getExternalProviders();
|
return await clashInterface.changeProxy(changeProxyParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<Connection>> getConnections() async {
|
||||||
|
final res = await clashInterface.getConnections();
|
||||||
|
final connectionsData = json.decode(res) as Map;
|
||||||
|
final connectionsRaw = connectionsData['connections'] as List? ?? [];
|
||||||
|
return connectionsRaw.map((e) => Connection.fromJson(e)).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
closeConnection(String id) {
|
||||||
|
clashInterface.closeConnection(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
closeConnections() {
|
||||||
|
clashInterface.closeConnections();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<ExternalProvider>> getExternalProviders() async {
|
||||||
final externalProvidersRawString =
|
final externalProvidersRawString =
|
||||||
externalProvidersRaw.cast<Utf8>().toDartString();
|
await clashInterface.getExternalProviders();
|
||||||
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>)
|
||||||
.map(
|
.map(
|
||||||
@@ -147,243 +144,81 @@ class ClashCore {
|
|||||||
)
|
)
|
||||||
.toList();
|
.toList();
|
||||||
return externalProviders;
|
return externalProviders;
|
||||||
});
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
ExternalProvider? getExternalProvider(String externalProviderName) {
|
Future<ExternalProvider?> getExternalProvider(
|
||||||
final externalProviderNameChar =
|
String externalProviderName) async {
|
||||||
externalProviderName.toNativeUtf8().cast<Char>();
|
final externalProvidersRawString =
|
||||||
final externalProviderRaw =
|
await clashInterface.getExternalProvider(externalProviderName);
|
||||||
clashFFI.getExternalProvider(externalProviderNameChar);
|
if (externalProvidersRawString == null) {
|
||||||
malloc.free(externalProviderNameChar);
|
return null;
|
||||||
final externalProviderRawString =
|
}
|
||||||
externalProviderRaw.cast<Utf8>().toDartString();
|
if (externalProvidersRawString.isEmpty) {
|
||||||
clashFFI.freeCString(externalProviderRaw);
|
return null;
|
||||||
if (externalProviderRawString.isEmpty) return null;
|
}
|
||||||
return ExternalProvider.fromJson(json.decode(externalProviderRawString));
|
return ExternalProvider.fromJson(json.decode(externalProvidersRawString));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String> updateGeoData({
|
Future<String> updateGeoData({
|
||||||
required String geoType,
|
required String geoType,
|
||||||
required String geoName,
|
required String geoName,
|
||||||
}) {
|
}) {
|
||||||
final completer = Completer<String>();
|
return clashInterface.updateGeoData(geoType: geoType, geoName: geoName);
|
||||||
final receiver = ReceivePort();
|
|
||||||
receiver.listen((message) {
|
|
||||||
if (!completer.isCompleted) {
|
|
||||||
completer.complete(message);
|
|
||||||
receiver.close();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
final geoTypeChar = geoType.toNativeUtf8().cast<Char>();
|
|
||||||
final geoNameChar = geoName.toNativeUtf8().cast<Char>();
|
|
||||||
clashFFI.updateGeoData(
|
|
||||||
geoTypeChar,
|
|
||||||
geoNameChar,
|
|
||||||
receiver.sendPort.nativePort,
|
|
||||||
);
|
|
||||||
malloc.free(geoTypeChar);
|
|
||||||
malloc.free(geoNameChar);
|
|
||||||
return completer.future;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String> sideLoadExternalProvider({
|
Future<String> sideLoadExternalProvider({
|
||||||
required String providerName,
|
required String providerName,
|
||||||
required String data,
|
required String data,
|
||||||
}) {
|
}) {
|
||||||
final completer = Completer<String>();
|
return clashInterface.sideLoadExternalProvider(
|
||||||
final receiver = ReceivePort();
|
providerName: providerName, data: data);
|
||||||
receiver.listen((message) {
|
|
||||||
if (!completer.isCompleted) {
|
|
||||||
completer.complete(message);
|
|
||||||
receiver.close();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
final providerNameChar = providerName.toNativeUtf8().cast<Char>();
|
|
||||||
final dataChar = data.toNativeUtf8().cast<Char>();
|
|
||||||
clashFFI.sideLoadExternalProvider(
|
|
||||||
providerNameChar,
|
|
||||||
dataChar,
|
|
||||||
receiver.sendPort.nativePort,
|
|
||||||
);
|
|
||||||
malloc.free(providerNameChar);
|
|
||||||
malloc.free(dataChar);
|
|
||||||
return completer.future;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String> updateExternalProvider({
|
Future<String> updateExternalProvider({
|
||||||
required String providerName,
|
required String providerName,
|
||||||
}) {
|
}) async {
|
||||||
final completer = Completer<String>();
|
return clashInterface.updateExternalProvider(providerName);
|
||||||
final receiver = ReceivePort();
|
|
||||||
receiver.listen((message) {
|
|
||||||
if (!completer.isCompleted) {
|
|
||||||
completer.complete(message);
|
|
||||||
receiver.close();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
final providerNameChar = providerName.toNativeUtf8().cast<Char>();
|
|
||||||
clashFFI.updateExternalProvider(
|
|
||||||
providerNameChar,
|
|
||||||
receiver.sendPort.nativePort,
|
|
||||||
);
|
|
||||||
malloc.free(providerNameChar);
|
|
||||||
return completer.future;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
changeProxy(ChangeProxyParams changeProxyParams) {
|
startListener() async {
|
||||||
final params = json.encode(changeProxyParams);
|
await clashInterface.startListener();
|
||||||
final paramsChar = params.toNativeUtf8().cast<Char>();
|
|
||||||
clashFFI.changeProxy(paramsChar);
|
|
||||||
malloc.free(paramsChar);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
start() {
|
stopListener() async {
|
||||||
clashFFI.start();
|
await clashInterface.stopListener();
|
||||||
}
|
}
|
||||||
|
|
||||||
stop() {
|
Future<Delay> getDelay(String proxyName) async {
|
||||||
clashFFI.stop();
|
final data = await clashInterface.asyncTestDelay(proxyName);
|
||||||
|
return Delay.fromJson(json.decode(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Delay> getDelay(String proxyName) {
|
Future<Traffic> getTraffic(bool value) async {
|
||||||
final delayParams = {
|
final trafficString = await clashInterface.getTraffic(value);
|
||||||
"proxy-name": proxyName,
|
return Traffic.fromMap(json.decode(trafficString));
|
||||||
"timeout": httpTimeoutDuration.inMilliseconds,
|
|
||||||
};
|
|
||||||
final completer = Completer<Delay>();
|
|
||||||
final receiver = ReceivePort();
|
|
||||||
receiver.listen((message) {
|
|
||||||
if (!completer.isCompleted) {
|
|
||||||
completer.complete(Delay.fromJson(json.decode(message)));
|
|
||||||
receiver.close();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
final delayParamsChar =
|
|
||||||
json.encode(delayParams).toNativeUtf8().cast<Char>();
|
|
||||||
clashFFI.asyncTestDelay(
|
|
||||||
delayParamsChar,
|
|
||||||
receiver.sendPort.nativePort,
|
|
||||||
);
|
|
||||||
malloc.free(delayParamsChar);
|
|
||||||
return completer.future;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
clearEffect(String profileId) {
|
Future<Traffic> getTotalTraffic(bool value) async {
|
||||||
final profileIdChar = profileId.toNativeUtf8().cast<Char>();
|
final totalTrafficString = await clashInterface.getTotalTraffic(value);
|
||||||
clashFFI.clearEffect(profileIdChar);
|
return Traffic.fromMap(json.decode(totalTrafficString));
|
||||||
malloc.free(profileIdChar);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
VersionInfo getVersionInfo() {
|
resetTraffic() {
|
||||||
final versionInfoRaw = clashFFI.getVersionInfo();
|
clashInterface.resetTraffic();
|
||||||
final versionInfo = json.decode(versionInfoRaw.cast<Utf8>().toDartString());
|
|
||||||
clashFFI.freeCString(versionInfoRaw);
|
|
||||||
return VersionInfo.fromJson(versionInfo);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setState(CoreState state) {
|
startLog() {
|
||||||
final stateChar = json.encode(state).toNativeUtf8().cast<Char>();
|
clashInterface.startLog();
|
||||||
clashFFI.setState(stateChar);
|
|
||||||
malloc.free(stateChar);
|
|
||||||
}
|
|
||||||
|
|
||||||
String getCurrentProfileName() {
|
|
||||||
final currentProfileRaw = clashFFI.getCurrentProfileName();
|
|
||||||
final currentProfile = currentProfileRaw.cast<Utf8>().toDartString();
|
|
||||||
clashFFI.freeCString(currentProfileRaw);
|
|
||||||
return currentProfile;
|
|
||||||
}
|
|
||||||
|
|
||||||
AndroidVpnOptions getAndroidVpnOptions() {
|
|
||||||
final vpnOptionsRaw = clashFFI.getAndroidVpnOptions();
|
|
||||||
final vpnOptions = json.decode(vpnOptionsRaw.cast<Utf8>().toDartString());
|
|
||||||
clashFFI.freeCString(vpnOptionsRaw);
|
|
||||||
return AndroidVpnOptions.fromJson(vpnOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
Traffic getTraffic() {
|
|
||||||
final trafficRaw = clashFFI.getTraffic();
|
|
||||||
final trafficMap = json.decode(trafficRaw.cast<Utf8>().toDartString());
|
|
||||||
clashFFI.freeCString(trafficRaw);
|
|
||||||
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() {
|
|
||||||
clashFFI.startLog();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
stopLog() {
|
stopLog() {
|
||||||
clashFFI.stopLog();
|
clashInterface.stopLog();
|
||||||
}
|
|
||||||
|
|
||||||
startTun(int fd, int port) {
|
|
||||||
if (!Platform.isAndroid) return;
|
|
||||||
clashFFI.startTUN(fd, port);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateDns(String dns) {
|
|
||||||
if (!Platform.isAndroid) return;
|
|
||||||
final dnsChar = dns.toNativeUtf8().cast<Char>();
|
|
||||||
clashFFI.updateDns(dnsChar);
|
|
||||||
malloc.free(dnsChar);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
requestGc() {
|
requestGc() {
|
||||||
clashFFI.forceGc();
|
clashInterface.forceGc();
|
||||||
}
|
|
||||||
|
|
||||||
void 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() {
|
|
||||||
final connectionsDataRaw = clashFFI.getConnections();
|
|
||||||
final connectionsData =
|
|
||||||
json.decode(connectionsDataRaw.cast<Utf8>().toDartString()) as Map;
|
|
||||||
clashFFI.freeCString(connectionsDataRaw);
|
|
||||||
final connectionsRaw = connectionsData['connections'] as List? ?? [];
|
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2362,21 +2362,46 @@ class ClashFFI {
|
|||||||
late final _updateDns =
|
late final _updateDns =
|
||||||
_updateDnsPtr.asFunction<void Function(ffi.Pointer<ffi.Char>)>();
|
_updateDnsPtr.asFunction<void Function(ffi.Pointer<ffi.Char>)>();
|
||||||
|
|
||||||
void start() {
|
void initNativeApiBridge(
|
||||||
return _start();
|
ffi.Pointer<ffi.Void> api,
|
||||||
|
) {
|
||||||
|
return _initNativeApiBridge(
|
||||||
|
api,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
late final _startPtr =
|
late final _initNativeApiBridgePtr =
|
||||||
_lookup<ffi.NativeFunction<ffi.Void Function()>>('start');
|
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Void>)>>(
|
||||||
late final _start = _startPtr.asFunction<void Function()>();
|
'initNativeApiBridge');
|
||||||
|
late final _initNativeApiBridge = _initNativeApiBridgePtr
|
||||||
|
.asFunction<void Function(ffi.Pointer<ffi.Void>)>();
|
||||||
|
|
||||||
void stop() {
|
void initMessage(
|
||||||
return _stop();
|
int port,
|
||||||
|
) {
|
||||||
|
return _initMessage(
|
||||||
|
port,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
late final _stopPtr =
|
late final _initMessagePtr =
|
||||||
_lookup<ffi.NativeFunction<ffi.Void Function()>>('stop');
|
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.LongLong)>>(
|
||||||
late final _stop = _stopPtr.asFunction<void Function()>();
|
'initMessage');
|
||||||
|
late final _initMessage = _initMessagePtr.asFunction<void Function(int)>();
|
||||||
|
|
||||||
|
void freeCString(
|
||||||
|
ffi.Pointer<ffi.Char> s,
|
||||||
|
) {
|
||||||
|
return _freeCString(
|
||||||
|
s,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
late final _freeCStringPtr =
|
||||||
|
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Char>)>>(
|
||||||
|
'freeCString');
|
||||||
|
late final _freeCString =
|
||||||
|
_freeCStringPtr.asFunction<void Function(ffi.Pointer<ffi.Char>)>();
|
||||||
|
|
||||||
int initClash(
|
int initClash(
|
||||||
ffi.Pointer<ffi.Char> homeDirStr,
|
ffi.Pointer<ffi.Char> homeDirStr,
|
||||||
@@ -2392,6 +2417,22 @@ class ClashFFI {
|
|||||||
late final _initClash =
|
late final _initClash =
|
||||||
_initClashPtr.asFunction<int Function(ffi.Pointer<ffi.Char>)>();
|
_initClashPtr.asFunction<int Function(ffi.Pointer<ffi.Char>)>();
|
||||||
|
|
||||||
|
void startListener() {
|
||||||
|
return _startListener();
|
||||||
|
}
|
||||||
|
|
||||||
|
late final _startListenerPtr =
|
||||||
|
_lookup<ffi.NativeFunction<ffi.Void Function()>>('startListener');
|
||||||
|
late final _startListener = _startListenerPtr.asFunction<void Function()>();
|
||||||
|
|
||||||
|
void stopListener() {
|
||||||
|
return _stopListener();
|
||||||
|
}
|
||||||
|
|
||||||
|
late final _stopListenerPtr =
|
||||||
|
_lookup<ffi.NativeFunction<ffi.Void Function()>>('stopListener');
|
||||||
|
late final _stopListener = _stopListenerPtr.asFunction<void Function()>();
|
||||||
|
|
||||||
int getIsInit() {
|
int getIsInit() {
|
||||||
return _getIsInit();
|
return _getIsInit();
|
||||||
}
|
}
|
||||||
@@ -2400,14 +2441,6 @@ class ClashFFI {
|
|||||||
_lookup<ffi.NativeFunction<GoUint8 Function()>>('getIsInit');
|
_lookup<ffi.NativeFunction<GoUint8 Function()>>('getIsInit');
|
||||||
late final _getIsInit = _getIsInitPtr.asFunction<int Function()>();
|
late final _getIsInit = _getIsInitPtr.asFunction<int Function()>();
|
||||||
|
|
||||||
int restartClash() {
|
|
||||||
return _restartClash();
|
|
||||||
}
|
|
||||||
|
|
||||||
late final _restartClashPtr =
|
|
||||||
_lookup<ffi.NativeFunction<GoUint8 Function()>>('restartClash');
|
|
||||||
late final _restartClash = _restartClashPtr.asFunction<int Function()>();
|
|
||||||
|
|
||||||
int shutdownClash() {
|
int shutdownClash() {
|
||||||
return _shutdownClash();
|
return _shutdownClash();
|
||||||
}
|
}
|
||||||
@@ -2458,20 +2491,6 @@ class ClashFFI {
|
|||||||
late final _updateConfig =
|
late final _updateConfig =
|
||||||
_updateConfigPtr.asFunction<void Function(ffi.Pointer<ffi.Char>, int)>();
|
_updateConfigPtr.asFunction<void Function(ffi.Pointer<ffi.Char>, int)>();
|
||||||
|
|
||||||
void clearEffect(
|
|
||||||
ffi.Pointer<ffi.Char> s,
|
|
||||||
) {
|
|
||||||
return _clearEffect(
|
|
||||||
s,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
late final _clearEffectPtr =
|
|
||||||
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Char>)>>(
|
|
||||||
'clearEffect');
|
|
||||||
late final _clearEffect =
|
|
||||||
_clearEffectPtr.asFunction<void Function(ffi.Pointer<ffi.Char>)>();
|
|
||||||
|
|
||||||
ffi.Pointer<ffi.Char> getProxies() {
|
ffi.Pointer<ffi.Char> getProxies() {
|
||||||
return _getProxies();
|
return _getProxies();
|
||||||
}
|
}
|
||||||
@@ -2484,37 +2503,48 @@ class ClashFFI {
|
|||||||
|
|
||||||
void changeProxy(
|
void changeProxy(
|
||||||
ffi.Pointer<ffi.Char> s,
|
ffi.Pointer<ffi.Char> s,
|
||||||
|
int port,
|
||||||
) {
|
) {
|
||||||
return _changeProxy(
|
return _changeProxy(
|
||||||
s,
|
s,
|
||||||
|
port,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
late final _changeProxyPtr =
|
late final _changeProxyPtr = _lookup<
|
||||||
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Char>)>>(
|
ffi.NativeFunction<
|
||||||
'changeProxy');
|
ffi.Void Function(
|
||||||
|
ffi.Pointer<ffi.Char>, ffi.LongLong)>>('changeProxy');
|
||||||
late final _changeProxy =
|
late final _changeProxy =
|
||||||
_changeProxyPtr.asFunction<void Function(ffi.Pointer<ffi.Char>)>();
|
_changeProxyPtr.asFunction<void Function(ffi.Pointer<ffi.Char>, int)>();
|
||||||
|
|
||||||
ffi.Pointer<ffi.Char> getTraffic() {
|
ffi.Pointer<ffi.Char> getTraffic(
|
||||||
return _getTraffic();
|
int port,
|
||||||
|
) {
|
||||||
|
return _getTraffic(
|
||||||
|
port,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
late final _getTrafficPtr =
|
late final _getTrafficPtr =
|
||||||
_lookup<ffi.NativeFunction<ffi.Pointer<ffi.Char> Function()>>(
|
_lookup<ffi.NativeFunction<ffi.Pointer<ffi.Char> Function(ffi.Int)>>(
|
||||||
'getTraffic');
|
'getTraffic');
|
||||||
late final _getTraffic =
|
late final _getTraffic =
|
||||||
_getTrafficPtr.asFunction<ffi.Pointer<ffi.Char> Function()>();
|
_getTrafficPtr.asFunction<ffi.Pointer<ffi.Char> Function(int)>();
|
||||||
|
|
||||||
ffi.Pointer<ffi.Char> getTotalTraffic() {
|
ffi.Pointer<ffi.Char> getTotalTraffic(
|
||||||
return _getTotalTraffic();
|
int port,
|
||||||
|
) {
|
||||||
|
return _getTotalTraffic(
|
||||||
|
port,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
late final _getTotalTrafficPtr =
|
late final _getTotalTrafficPtr =
|
||||||
_lookup<ffi.NativeFunction<ffi.Pointer<ffi.Char> Function()>>(
|
_lookup<ffi.NativeFunction<ffi.Pointer<ffi.Char> Function(ffi.Int)>>(
|
||||||
'getTotalTraffic');
|
'getTotalTraffic');
|
||||||
late final _getTotalTraffic =
|
late final _getTotalTraffic =
|
||||||
_getTotalTrafficPtr.asFunction<ffi.Pointer<ffi.Char> Function()>();
|
_getTotalTrafficPtr.asFunction<ffi.Pointer<ffi.Char> Function(int)>();
|
||||||
|
|
||||||
void resetTraffic() {
|
void resetTraffic() {
|
||||||
return _resetTraffic();
|
return _resetTraffic();
|
||||||
@@ -2541,16 +2571,6 @@ class ClashFFI {
|
|||||||
late final _asyncTestDelay = _asyncTestDelayPtr
|
late final _asyncTestDelay = _asyncTestDelayPtr
|
||||||
.asFunction<void Function(ffi.Pointer<ffi.Char>, int)>();
|
.asFunction<void Function(ffi.Pointer<ffi.Char>, int)>();
|
||||||
|
|
||||||
ffi.Pointer<ffi.Char> getVersionInfo() {
|
|
||||||
return _getVersionInfo();
|
|
||||||
}
|
|
||||||
|
|
||||||
late final _getVersionInfoPtr =
|
|
||||||
_lookup<ffi.NativeFunction<ffi.Pointer<ffi.Char> Function()>>(
|
|
||||||
'getVersionInfo');
|
|
||||||
late final _getVersionInfo =
|
|
||||||
_getVersionInfoPtr.asFunction<ffi.Pointer<ffi.Char> Function()>();
|
|
||||||
|
|
||||||
ffi.Pointer<ffi.Char> getConnections() {
|
ffi.Pointer<ffi.Char> getConnections() {
|
||||||
return _getConnections();
|
return _getConnections();
|
||||||
}
|
}
|
||||||
@@ -2595,10 +2615,10 @@ class ClashFFI {
|
|||||||
_getExternalProvidersPtr.asFunction<ffi.Pointer<ffi.Char> Function()>();
|
_getExternalProvidersPtr.asFunction<ffi.Pointer<ffi.Char> Function()>();
|
||||||
|
|
||||||
ffi.Pointer<ffi.Char> getExternalProvider(
|
ffi.Pointer<ffi.Char> getExternalProvider(
|
||||||
ffi.Pointer<ffi.Char> name,
|
ffi.Pointer<ffi.Char> externalProviderNameChar,
|
||||||
) {
|
) {
|
||||||
return _getExternalProvider(
|
return _getExternalProvider(
|
||||||
name,
|
externalProviderNameChar,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2610,13 +2630,13 @@ class ClashFFI {
|
|||||||
.asFunction<ffi.Pointer<ffi.Char> Function(ffi.Pointer<ffi.Char>)>();
|
.asFunction<ffi.Pointer<ffi.Char> Function(ffi.Pointer<ffi.Char>)>();
|
||||||
|
|
||||||
void updateGeoData(
|
void updateGeoData(
|
||||||
ffi.Pointer<ffi.Char> geoType,
|
ffi.Pointer<ffi.Char> geoTypeChar,
|
||||||
ffi.Pointer<ffi.Char> geoName,
|
ffi.Pointer<ffi.Char> geoNameChar,
|
||||||
int port,
|
int port,
|
||||||
) {
|
) {
|
||||||
return _updateGeoData(
|
return _updateGeoData(
|
||||||
geoType,
|
geoTypeChar,
|
||||||
geoName,
|
geoNameChar,
|
||||||
port,
|
port,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -2629,11 +2649,11 @@ class ClashFFI {
|
|||||||
void Function(ffi.Pointer<ffi.Char>, ffi.Pointer<ffi.Char>, int)>();
|
void Function(ffi.Pointer<ffi.Char>, ffi.Pointer<ffi.Char>, int)>();
|
||||||
|
|
||||||
void updateExternalProvider(
|
void updateExternalProvider(
|
||||||
ffi.Pointer<ffi.Char> providerName,
|
ffi.Pointer<ffi.Char> providerNameChar,
|
||||||
int port,
|
int port,
|
||||||
) {
|
) {
|
||||||
return _updateExternalProvider(
|
return _updateExternalProvider(
|
||||||
providerName,
|
providerNameChar,
|
||||||
port,
|
port,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -2646,13 +2666,13 @@ class ClashFFI {
|
|||||||
.asFunction<void Function(ffi.Pointer<ffi.Char>, int)>();
|
.asFunction<void Function(ffi.Pointer<ffi.Char>, int)>();
|
||||||
|
|
||||||
void sideLoadExternalProvider(
|
void sideLoadExternalProvider(
|
||||||
ffi.Pointer<ffi.Char> providerName,
|
ffi.Pointer<ffi.Char> providerNameChar,
|
||||||
ffi.Pointer<ffi.Char> data,
|
ffi.Pointer<ffi.Char> dataChar,
|
||||||
int port,
|
int port,
|
||||||
) {
|
) {
|
||||||
return _sideLoadExternalProvider(
|
return _sideLoadExternalProvider(
|
||||||
providerName,
|
providerNameChar,
|
||||||
data,
|
dataChar,
|
||||||
port,
|
port,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -2665,47 +2685,6 @@ class ClashFFI {
|
|||||||
_sideLoadExternalProviderPtr.asFunction<
|
_sideLoadExternalProviderPtr.asFunction<
|
||||||
void Function(ffi.Pointer<ffi.Char>, ffi.Pointer<ffi.Char>, int)>();
|
void Function(ffi.Pointer<ffi.Char>, ffi.Pointer<ffi.Char>, int)>();
|
||||||
|
|
||||||
void initNativeApiBridge(
|
|
||||||
ffi.Pointer<ffi.Void> api,
|
|
||||||
) {
|
|
||||||
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 _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,
|
|
||||||
) {
|
|
||||||
return _freeCString(
|
|
||||||
s,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
late final _freeCStringPtr =
|
|
||||||
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Char>)>>(
|
|
||||||
'freeCString');
|
|
||||||
late final _freeCString =
|
|
||||||
_freeCStringPtr.asFunction<void Function(ffi.Pointer<ffi.Char>)>();
|
|
||||||
|
|
||||||
void startLog() {
|
void startLog() {
|
||||||
return _startLog();
|
return _startLog();
|
||||||
}
|
}
|
||||||
@@ -2722,6 +2701,51 @@ class ClashFFI {
|
|||||||
_lookup<ffi.NativeFunction<ffi.Void Function()>>('stopLog');
|
_lookup<ffi.NativeFunction<ffi.Void Function()>>('stopLog');
|
||||||
late final _stopLog = _stopLogPtr.asFunction<void Function()>();
|
late final _stopLog = _stopLogPtr.asFunction<void Function()>();
|
||||||
|
|
||||||
|
void startTUN(
|
||||||
|
int fd,
|
||||||
|
int port,
|
||||||
|
) {
|
||||||
|
return _startTUN(
|
||||||
|
fd,
|
||||||
|
port,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
late final _startTUNPtr =
|
||||||
|
_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();
|
||||||
|
}
|
||||||
|
|
||||||
|
late final _stopTunPtr =
|
||||||
|
_lookup<ffi.NativeFunction<ffi.Void Function()>>('stopTun');
|
||||||
|
late final _stopTun = _stopTunPtr.asFunction<void Function()>();
|
||||||
|
|
||||||
|
void setFdMap(
|
||||||
|
int fd,
|
||||||
|
) {
|
||||||
|
return _setFdMap(
|
||||||
|
fd,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
late final _setFdMapPtr =
|
||||||
|
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.Long)>>('setFdMap');
|
||||||
|
late final _setFdMap = _setFdMapPtr.asFunction<void Function(int)>();
|
||||||
|
|
||||||
void setProcessMap(
|
void setProcessMap(
|
||||||
ffi.Pointer<ffi.Char> s,
|
ffi.Pointer<ffi.Char> s,
|
||||||
) {
|
) {
|
||||||
@@ -2769,51 +2793,6 @@ class ClashFFI {
|
|||||||
'setState');
|
'setState');
|
||||||
late final _setState =
|
late final _setState =
|
||||||
_setStatePtr.asFunction<void Function(ffi.Pointer<ffi.Char>)>();
|
_setStatePtr.asFunction<void Function(ffi.Pointer<ffi.Char>)>();
|
||||||
|
|
||||||
void startTUN(
|
|
||||||
int fd,
|
|
||||||
int port,
|
|
||||||
) {
|
|
||||||
return _startTUN(
|
|
||||||
fd,
|
|
||||||
port,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
late final _startTUNPtr =
|
|
||||||
_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();
|
|
||||||
}
|
|
||||||
|
|
||||||
late final _stopTunPtr =
|
|
||||||
_lookup<ffi.NativeFunction<ffi.Void Function()>>('stopTun');
|
|
||||||
late final _stopTun = _stopTunPtr.asFunction<void Function()>();
|
|
||||||
|
|
||||||
void setFdMap(
|
|
||||||
int fd,
|
|
||||||
) {
|
|
||||||
return _setFdMap(
|
|
||||||
fd,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
late final _setFdMapPtr =
|
|
||||||
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.Long)>>('setFdMap');
|
|
||||||
late final _setFdMap = _setFdMapPtr.asFunction<void Function(int)>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final class __mbstate_t extends ffi.Union {
|
final class __mbstate_t extends ffi.Union {
|
||||||
|
|||||||
59
lib/clash/interface.dart
Normal file
59
lib/clash/interface.dart
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:fl_clash/models/models.dart';
|
||||||
|
|
||||||
|
mixin ClashInterface {
|
||||||
|
FutureOr<bool> init(String homeDir);
|
||||||
|
|
||||||
|
FutureOr<void> shutdown();
|
||||||
|
|
||||||
|
FutureOr<bool> get isInit;
|
||||||
|
|
||||||
|
forceGc();
|
||||||
|
|
||||||
|
FutureOr<String> validateConfig(String data);
|
||||||
|
|
||||||
|
Future<String> asyncTestDelay(String proxyName);
|
||||||
|
|
||||||
|
FutureOr<String> updateConfig(UpdateConfigParams updateConfigParams);
|
||||||
|
|
||||||
|
FutureOr<String> getProxies();
|
||||||
|
|
||||||
|
FutureOr<String> changeProxy(ChangeProxyParams changeProxyParams);
|
||||||
|
|
||||||
|
Future<bool> startListener();
|
||||||
|
|
||||||
|
Future<bool> stopListener();
|
||||||
|
|
||||||
|
FutureOr<String> getExternalProviders();
|
||||||
|
|
||||||
|
FutureOr<String>? getExternalProvider(String externalProviderName);
|
||||||
|
|
||||||
|
Future<String> updateGeoData({
|
||||||
|
required String geoType,
|
||||||
|
required String geoName,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<String> sideLoadExternalProvider({
|
||||||
|
required String providerName,
|
||||||
|
required String data,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<String> updateExternalProvider(String providerName);
|
||||||
|
|
||||||
|
FutureOr<String> getTraffic(bool value);
|
||||||
|
|
||||||
|
FutureOr<String> getTotalTraffic(bool value);
|
||||||
|
|
||||||
|
resetTraffic();
|
||||||
|
|
||||||
|
startLog();
|
||||||
|
|
||||||
|
stopLog();
|
||||||
|
|
||||||
|
FutureOr<String> getConnections();
|
||||||
|
|
||||||
|
FutureOr<bool> closeConnection(String id);
|
||||||
|
|
||||||
|
FutureOr<bool> closeConnections();
|
||||||
|
}
|
||||||
367
lib/clash/lib.dart
Normal file
367
lib/clash/lib.dart
Normal file
@@ -0,0 +1,367 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:ffi';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'dart:isolate';
|
||||||
|
|
||||||
|
import 'package:ffi/ffi.dart';
|
||||||
|
import 'package:fl_clash/common/constant.dart';
|
||||||
|
import 'package:fl_clash/models/models.dart';
|
||||||
|
|
||||||
|
import 'generated/clash_ffi.dart';
|
||||||
|
import 'interface.dart';
|
||||||
|
|
||||||
|
class ClashLib with ClashInterface {
|
||||||
|
static ClashLib? _instance;
|
||||||
|
final receiver = ReceivePort();
|
||||||
|
|
||||||
|
late final ClashFFI clashFFI;
|
||||||
|
|
||||||
|
late final DynamicLibrary lib;
|
||||||
|
|
||||||
|
ClashLib._internal() {
|
||||||
|
lib = DynamicLibrary.open("libclash.so");
|
||||||
|
clashFFI = ClashFFI(lib);
|
||||||
|
clashFFI.initNativeApiBridge(
|
||||||
|
NativeApi.initializeApiDLData,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
factory ClashLib() {
|
||||||
|
_instance ??= ClashLib._internal();
|
||||||
|
return _instance!;
|
||||||
|
}
|
||||||
|
|
||||||
|
initMessage() {
|
||||||
|
clashFFI.initMessage(
|
||||||
|
receiver.sendPort.nativePort,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool init(String homeDir) {
|
||||||
|
final homeDirChar = homeDir.toNativeUtf8().cast<Char>();
|
||||||
|
final isInit = clashFFI.initClash(homeDirChar) == 1;
|
||||||
|
malloc.free(homeDirChar);
|
||||||
|
return isInit;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
shutdown() async {
|
||||||
|
clashFFI.shutdownClash();
|
||||||
|
lib.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get isInit => clashFFI.getIsInit() == 1;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String> validateConfig(String data) {
|
||||||
|
final completer = Completer<String>();
|
||||||
|
final receiver = ReceivePort();
|
||||||
|
receiver.listen((message) {
|
||||||
|
if (!completer.isCompleted) {
|
||||||
|
completer.complete(message);
|
||||||
|
receiver.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
final dataChar = data.toNativeUtf8().cast<Char>();
|
||||||
|
clashFFI.validateConfig(
|
||||||
|
dataChar,
|
||||||
|
receiver.sendPort.nativePort,
|
||||||
|
);
|
||||||
|
malloc.free(dataChar);
|
||||||
|
return completer.future;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String> updateConfig(UpdateConfigParams updateConfigParams) {
|
||||||
|
final completer = Completer<String>();
|
||||||
|
final receiver = ReceivePort();
|
||||||
|
receiver.listen((message) {
|
||||||
|
if (!completer.isCompleted) {
|
||||||
|
completer.complete(message);
|
||||||
|
receiver.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
final params = json.encode(updateConfigParams);
|
||||||
|
final paramsChar = params.toNativeUtf8().cast<Char>();
|
||||||
|
clashFFI.updateConfig(
|
||||||
|
paramsChar,
|
||||||
|
receiver.sendPort.nativePort,
|
||||||
|
);
|
||||||
|
malloc.free(paramsChar);
|
||||||
|
return completer.future;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String getProxies() {
|
||||||
|
final proxiesRaw = clashFFI.getProxies();
|
||||||
|
final proxiesRawString = proxiesRaw.cast<Utf8>().toDartString();
|
||||||
|
clashFFI.freeCString(proxiesRaw);
|
||||||
|
return proxiesRawString;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String getExternalProviders() {
|
||||||
|
final externalProvidersRaw = clashFFI.getExternalProviders();
|
||||||
|
final externalProvidersRawString =
|
||||||
|
externalProvidersRaw.cast<Utf8>().toDartString();
|
||||||
|
clashFFI.freeCString(externalProvidersRaw);
|
||||||
|
return externalProvidersRawString;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String getExternalProvider(String externalProviderName) {
|
||||||
|
final externalProviderNameChar =
|
||||||
|
externalProviderName.toNativeUtf8().cast<Char>();
|
||||||
|
final externalProviderRaw =
|
||||||
|
clashFFI.getExternalProvider(externalProviderNameChar);
|
||||||
|
malloc.free(externalProviderNameChar);
|
||||||
|
final externalProviderRawString =
|
||||||
|
externalProviderRaw.cast<Utf8>().toDartString();
|
||||||
|
clashFFI.freeCString(externalProviderRaw);
|
||||||
|
return externalProviderRawString;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String> updateGeoData({
|
||||||
|
required String geoType,
|
||||||
|
required String geoName,
|
||||||
|
}) {
|
||||||
|
final completer = Completer<String>();
|
||||||
|
final receiver = ReceivePort();
|
||||||
|
receiver.listen((message) {
|
||||||
|
if (!completer.isCompleted) {
|
||||||
|
completer.complete(message);
|
||||||
|
receiver.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
final geoTypeChar = geoType.toNativeUtf8().cast<Char>();
|
||||||
|
final geoNameChar = geoName.toNativeUtf8().cast<Char>();
|
||||||
|
clashFFI.updateGeoData(
|
||||||
|
geoTypeChar,
|
||||||
|
geoNameChar,
|
||||||
|
receiver.sendPort.nativePort,
|
||||||
|
);
|
||||||
|
malloc.free(geoTypeChar);
|
||||||
|
malloc.free(geoNameChar);
|
||||||
|
return completer.future;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String> sideLoadExternalProvider({
|
||||||
|
required String providerName,
|
||||||
|
required String data,
|
||||||
|
}) {
|
||||||
|
final completer = Completer<String>();
|
||||||
|
final receiver = ReceivePort();
|
||||||
|
receiver.listen((message) {
|
||||||
|
if (!completer.isCompleted) {
|
||||||
|
completer.complete(message);
|
||||||
|
receiver.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
final providerNameChar = providerName.toNativeUtf8().cast<Char>();
|
||||||
|
final dataChar = data.toNativeUtf8().cast<Char>();
|
||||||
|
clashFFI.sideLoadExternalProvider(
|
||||||
|
providerNameChar,
|
||||||
|
dataChar,
|
||||||
|
receiver.sendPort.nativePort,
|
||||||
|
);
|
||||||
|
malloc.free(providerNameChar);
|
||||||
|
malloc.free(dataChar);
|
||||||
|
return completer.future;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String> updateExternalProvider(String providerName) {
|
||||||
|
final completer = Completer<String>();
|
||||||
|
final receiver = ReceivePort();
|
||||||
|
receiver.listen((message) {
|
||||||
|
if (!completer.isCompleted) {
|
||||||
|
completer.complete(message);
|
||||||
|
receiver.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
final providerNameChar = providerName.toNativeUtf8().cast<Char>();
|
||||||
|
clashFFI.updateExternalProvider(
|
||||||
|
providerNameChar,
|
||||||
|
receiver.sendPort.nativePort,
|
||||||
|
);
|
||||||
|
malloc.free(providerNameChar);
|
||||||
|
return completer.future;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String> changeProxy(ChangeProxyParams changeProxyParams) {
|
||||||
|
final completer = Completer<String>();
|
||||||
|
final receiver = ReceivePort();
|
||||||
|
receiver.listen((message) {
|
||||||
|
if (!completer.isCompleted) {
|
||||||
|
completer.complete(message);
|
||||||
|
receiver.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
final params = json.encode(changeProxyParams);
|
||||||
|
final paramsChar = params.toNativeUtf8().cast<Char>();
|
||||||
|
clashFFI.changeProxy(
|
||||||
|
paramsChar,
|
||||||
|
receiver.sendPort.nativePort,
|
||||||
|
);
|
||||||
|
malloc.free(paramsChar);
|
||||||
|
return completer.future;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String getConnections() {
|
||||||
|
final connectionsDataRaw = clashFFI.getConnections();
|
||||||
|
final connectionsString = connectionsDataRaw.cast<Utf8>().toDartString();
|
||||||
|
clashFFI.freeCString(connectionsDataRaw);
|
||||||
|
return connectionsString;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
closeConnection(String id) {
|
||||||
|
final idChar = id.toNativeUtf8().cast<Char>();
|
||||||
|
clashFFI.closeConnection(idChar);
|
||||||
|
malloc.free(idChar);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
closeConnections() {
|
||||||
|
clashFFI.closeConnections();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
startListener() async {
|
||||||
|
clashFFI.startListener();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
stopListener() async {
|
||||||
|
clashFFI.stopListener();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String> asyncTestDelay(String proxyName) {
|
||||||
|
final delayParams = {
|
||||||
|
"proxy-name": proxyName,
|
||||||
|
"timeout": httpTimeoutDuration.inMilliseconds,
|
||||||
|
};
|
||||||
|
final completer = Completer<String>();
|
||||||
|
final receiver = ReceivePort();
|
||||||
|
receiver.listen((message) {
|
||||||
|
if (!completer.isCompleted) {
|
||||||
|
completer.complete(message);
|
||||||
|
receiver.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
final delayParamsChar =
|
||||||
|
json.encode(delayParams).toNativeUtf8().cast<Char>();
|
||||||
|
clashFFI.asyncTestDelay(
|
||||||
|
delayParamsChar,
|
||||||
|
receiver.sendPort.nativePort,
|
||||||
|
);
|
||||||
|
malloc.free(delayParamsChar);
|
||||||
|
return completer.future;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String getTraffic(bool value) {
|
||||||
|
final trafficRaw = clashFFI.getTraffic(value ? 1 : 0);
|
||||||
|
final trafficString = trafficRaw.cast<Utf8>().toDartString();
|
||||||
|
clashFFI.freeCString(trafficRaw);
|
||||||
|
return trafficString;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String getTotalTraffic(bool value) {
|
||||||
|
final trafficRaw = clashFFI.getTotalTraffic(value ? 1 : 0);
|
||||||
|
clashFFI.freeCString(trafficRaw);
|
||||||
|
return trafficRaw.cast<Utf8>().toDartString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void resetTraffic() {
|
||||||
|
clashFFI.resetTraffic();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void startLog() {
|
||||||
|
clashFFI.startLog();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
stopLog() {
|
||||||
|
clashFFI.stopLog();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
forceGc() {
|
||||||
|
clashFFI.forceGc();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Android
|
||||||
|
|
||||||
|
startTun(int fd, int port) {
|
||||||
|
if (!Platform.isAndroid) return;
|
||||||
|
clashFFI.startTUN(fd, port);
|
||||||
|
}
|
||||||
|
|
||||||
|
stopTun() {
|
||||||
|
clashFFI.stopTun();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateDns(String dns) {
|
||||||
|
if (!Platform.isAndroid) return;
|
||||||
|
final dnsChar = dns.toNativeUtf8().cast<Char>();
|
||||||
|
clashFFI.updateDns(dnsChar);
|
||||||
|
malloc.free(dnsChar);
|
||||||
|
}
|
||||||
|
|
||||||
|
setProcessMap(ProcessMapItem processMapItem) {
|
||||||
|
final processMapItemChar =
|
||||||
|
json.encode(processMapItem).toNativeUtf8().cast<Char>();
|
||||||
|
clashFFI.setProcessMap(processMapItemChar);
|
||||||
|
malloc.free(processMapItemChar);
|
||||||
|
}
|
||||||
|
|
||||||
|
setState(CoreState state) {
|
||||||
|
final stateChar = json.encode(state).toNativeUtf8().cast<Char>();
|
||||||
|
clashFFI.setState(stateChar);
|
||||||
|
malloc.free(stateChar);
|
||||||
|
}
|
||||||
|
|
||||||
|
String getCurrentProfileName() {
|
||||||
|
final currentProfileRaw = clashFFI.getCurrentProfileName();
|
||||||
|
final currentProfile = currentProfileRaw.cast<Utf8>().toDartString();
|
||||||
|
clashFFI.freeCString(currentProfileRaw);
|
||||||
|
return currentProfile;
|
||||||
|
}
|
||||||
|
|
||||||
|
AndroidVpnOptions getAndroidVpnOptions() {
|
||||||
|
final vpnOptionsRaw = clashFFI.getAndroidVpnOptions();
|
||||||
|
final vpnOptions = json.decode(vpnOptionsRaw.cast<Utf8>().toDartString());
|
||||||
|
clashFFI.freeCString(vpnOptionsRaw);
|
||||||
|
return AndroidVpnOptions.fromJson(vpnOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final clashLib = Platform.isAndroid ? ClashLib() : null;
|
||||||
@@ -1,21 +1,18 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:fl_clash/clash/clash.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';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
import 'core.dart';
|
|
||||||
|
|
||||||
class ClashMessage {
|
class ClashMessage {
|
||||||
StreamSubscription? subscription;
|
final controller = StreamController();
|
||||||
|
|
||||||
ClashMessage._() {
|
ClashMessage._() {
|
||||||
if (subscription != null) {
|
clashLib?.receiver.listen(controller.add);
|
||||||
subscription!.cancel();
|
controller.stream.listen(
|
||||||
subscription = null;
|
(message) {
|
||||||
}
|
|
||||||
subscription = ClashCore.receiver.listen((message) {
|
|
||||||
final m = AppMessage.fromJson(json.decode(message));
|
final m = AppMessage.fromJson(json.decode(message));
|
||||||
for (final AppMessageListener listener in _listeners) {
|
for (final AppMessageListener listener in _listeners) {
|
||||||
switch (m.type) {
|
switch (m.type) {
|
||||||
@@ -36,7 +33,8 @@ class ClashMessage {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static final ClashMessage instance = ClashMessage._();
|
static final ClashMessage instance = ClashMessage._();
|
||||||
|
|||||||
@@ -1,54 +1,414 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:fl_clash/clash/clash.dart';
|
||||||
|
import 'package:fl_clash/clash/interface.dart';
|
||||||
import 'package:fl_clash/common/common.dart';
|
import 'package:fl_clash/common/common.dart';
|
||||||
import 'package:fl_clash/models/models.dart';
|
import 'package:fl_clash/enum/enum.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:fl_clash/models/core.dart';
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:path/path.dart';
|
|
||||||
|
|
||||||
import 'core.dart';
|
class ClashService with ClashInterface {
|
||||||
|
static ClashService? _instance;
|
||||||
|
|
||||||
class ClashService {
|
Completer<ServerSocket> serverCompleter = Completer();
|
||||||
Future<void> initGeo() async {
|
|
||||||
final homePath = await appPath.getHomeDirPath();
|
Completer<Socket> socketCompleter = Completer();
|
||||||
final homeDir = Directory(homePath);
|
|
||||||
final isExists = await homeDir.exists();
|
Map<String, Completer> callbackCompleterMap = {};
|
||||||
if (!isExists) {
|
|
||||||
await homeDir.create(recursive: true);
|
Process? process;
|
||||||
|
|
||||||
|
factory ClashService() {
|
||||||
|
_instance ??= ClashService._internal();
|
||||||
|
return _instance!;
|
||||||
}
|
}
|
||||||
const geoFileNameList = [
|
|
||||||
mmdbFileName,
|
ClashService._internal() {
|
||||||
geoIpFileName,
|
_createServer();
|
||||||
geoSiteFileName,
|
startCore();
|
||||||
asnFileName,
|
}
|
||||||
];
|
|
||||||
try {
|
_createServer() async {
|
||||||
for (final geoFileName in geoFileNameList) {
|
final address = !Platform.isWindows
|
||||||
final geoFile = File(
|
? InternetAddress(
|
||||||
join(homePath, geoFileName),
|
unixSocketPath,
|
||||||
|
type: InternetAddressType.unix,
|
||||||
|
)
|
||||||
|
: InternetAddress(
|
||||||
|
localhost,
|
||||||
|
type: InternetAddressType.IPv4,
|
||||||
|
);
|
||||||
|
await _deleteSocketFile();
|
||||||
|
final server = await ServerSocket.bind(
|
||||||
|
address,
|
||||||
|
0,
|
||||||
|
shared: true,
|
||||||
|
);
|
||||||
|
serverCompleter.complete(server);
|
||||||
|
await for (final socket in server) {
|
||||||
|
await _destroySocket();
|
||||||
|
socketCompleter.complete(socket);
|
||||||
|
socket
|
||||||
|
.transform(
|
||||||
|
StreamTransformer<Uint8List, String>.fromHandlers(
|
||||||
|
handleData: (Uint8List data, EventSink<String> sink) {
|
||||||
|
sink.add(utf8.decode(data, allowMalformed: true));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.transform(LineSplitter())
|
||||||
|
.listen(
|
||||||
|
(data) {
|
||||||
|
_handleAction(
|
||||||
|
Action.fromJson(
|
||||||
|
json.decode(data.trim()),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
final isExists = await geoFile.exists();
|
|
||||||
if (isExists) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
final data = await rootBundle.load('assets/data/$geoFileName');
|
|
||||||
List<int> bytes = data.buffer.asUint8List();
|
|
||||||
await geoFile.writeAsBytes(bytes, flush: true);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
debugPrint("$e");
|
|
||||||
exit(0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> init({
|
startCore() async {
|
||||||
required ClashConfig clashConfig,
|
if (process != null) {
|
||||||
required Config config,
|
await shutdown();
|
||||||
|
}
|
||||||
|
final serverSocket = await serverCompleter.future;
|
||||||
|
final arg = Platform.isWindows
|
||||||
|
? "${serverSocket.port}"
|
||||||
|
: serverSocket.address.address;
|
||||||
|
bool isSuccess = false;
|
||||||
|
if (Platform.isWindows && await system.checkIsAdmin()) {
|
||||||
|
isSuccess = await request.startCoreByHelper(arg);
|
||||||
|
}
|
||||||
|
if (isSuccess) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
process = await Process.start(
|
||||||
|
appPath.corePath,
|
||||||
|
[
|
||||||
|
arg,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
process!.stdout.listen((_) {});
|
||||||
|
}
|
||||||
|
|
||||||
|
_deleteSocketFile() async {
|
||||||
|
if (!Platform.isWindows) {
|
||||||
|
final file = File(unixSocketPath);
|
||||||
|
if (await file.exists()) {
|
||||||
|
await file.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_destroySocket() async {
|
||||||
|
if (socketCompleter.isCompleted) {
|
||||||
|
final lastSocket = await socketCompleter.future;
|
||||||
|
await lastSocket.close();
|
||||||
|
socketCompleter = Completer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_handleAction(Action action) {
|
||||||
|
final completer = callbackCompleterMap[action.id];
|
||||||
|
switch (action.method) {
|
||||||
|
case ActionMethod.initClash:
|
||||||
|
case ActionMethod.shutdown:
|
||||||
|
case ActionMethod.getIsInit:
|
||||||
|
case ActionMethod.startListener:
|
||||||
|
case ActionMethod.resetTraffic:
|
||||||
|
case ActionMethod.closeConnections:
|
||||||
|
case ActionMethod.closeConnection:
|
||||||
|
case ActionMethod.stopListener:
|
||||||
|
completer?.complete(action.data as bool);
|
||||||
|
return;
|
||||||
|
case ActionMethod.changeProxy:
|
||||||
|
case ActionMethod.getProxies:
|
||||||
|
case ActionMethod.getTraffic:
|
||||||
|
case ActionMethod.getTotalTraffic:
|
||||||
|
case ActionMethod.asyncTestDelay:
|
||||||
|
case ActionMethod.getConnections:
|
||||||
|
case ActionMethod.getExternalProviders:
|
||||||
|
case ActionMethod.getExternalProvider:
|
||||||
|
case ActionMethod.validateConfig:
|
||||||
|
case ActionMethod.updateConfig:
|
||||||
|
case ActionMethod.updateGeoData:
|
||||||
|
case ActionMethod.updateExternalProvider:
|
||||||
|
case ActionMethod.sideLoadExternalProvider:
|
||||||
|
completer?.complete(action.data as String);
|
||||||
|
return;
|
||||||
|
case ActionMethod.message:
|
||||||
|
clashMessage.controller.add(action.data as String);
|
||||||
|
return;
|
||||||
|
case ActionMethod.forceGc:
|
||||||
|
case ActionMethod.startLog:
|
||||||
|
case ActionMethod.stopLog:
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<T> _invoke<T>({
|
||||||
|
required ActionMethod method,
|
||||||
|
dynamic data,
|
||||||
|
Duration? timeout,
|
||||||
|
FutureOr<T> Function()? onTimeout,
|
||||||
}) async {
|
}) async {
|
||||||
await initGeo();
|
final id = "${method.name}#${other.id}";
|
||||||
final homeDirPath = await appPath.getHomeDirPath();
|
final socket = await socketCompleter.future;
|
||||||
final isInit = clashCore.init(homeDirPath);
|
callbackCompleterMap[id] = Completer<T>();
|
||||||
return isInit;
|
socket.writeln(
|
||||||
|
json.encode(
|
||||||
|
Action(
|
||||||
|
id: id,
|
||||||
|
method: method,
|
||||||
|
data: data,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return (callbackCompleterMap[id] as Completer<T>).safeFuture(
|
||||||
|
timeout: timeout,
|
||||||
|
onLast: () {
|
||||||
|
callbackCompleterMap.remove(id);
|
||||||
|
},
|
||||||
|
onTimeout: onTimeout,
|
||||||
|
functionName: id,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_prueInvoke({
|
||||||
|
required ActionMethod method,
|
||||||
|
dynamic data,
|
||||||
|
}) async {
|
||||||
|
final id = "${method.name}#${other.id}";
|
||||||
|
final socket = await socketCompleter.future;
|
||||||
|
socket.writeln(
|
||||||
|
json.encode(
|
||||||
|
Action(
|
||||||
|
id: id,
|
||||||
|
method: method,
|
||||||
|
data: data,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> init(String homeDir) {
|
||||||
|
return _invoke<bool>(
|
||||||
|
method: ActionMethod.initClash,
|
||||||
|
data: homeDir,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
shutdown() async {
|
||||||
|
await _invoke<bool>(
|
||||||
|
method: ActionMethod.shutdown,
|
||||||
|
);
|
||||||
|
if (Platform.isWindows) {
|
||||||
|
await request.stopCoreByHelper();
|
||||||
|
}
|
||||||
|
await _destroySocket();
|
||||||
|
process?.kill();
|
||||||
|
process = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> get isInit {
|
||||||
|
return _invoke<bool>(
|
||||||
|
method: ActionMethod.getIsInit,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
forceGc() {
|
||||||
|
_prueInvoke(method: ActionMethod.forceGc);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureOr<String> validateConfig(String data) {
|
||||||
|
return _invoke<String>(
|
||||||
|
method: ActionMethod.validateConfig,
|
||||||
|
data: data,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String> updateConfig(UpdateConfigParams updateConfigParams) async {
|
||||||
|
return await _invoke<String>(
|
||||||
|
method: ActionMethod.updateConfig,
|
||||||
|
data: json.encode(updateConfigParams),
|
||||||
|
timeout: const Duration(seconds: 20),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String> getProxies() {
|
||||||
|
return _invoke<String>(
|
||||||
|
method: ActionMethod.getProxies,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureOr<String> changeProxy(ChangeProxyParams changeProxyParams) {
|
||||||
|
return _invoke<String>(
|
||||||
|
method: ActionMethod.changeProxy,
|
||||||
|
data: json.encode(changeProxyParams),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureOr<String> getExternalProviders() {
|
||||||
|
return _invoke<String>(
|
||||||
|
method: ActionMethod.getExternalProviders,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureOr<String> getExternalProvider(String externalProviderName) {
|
||||||
|
return _invoke<String>(
|
||||||
|
method: ActionMethod.getExternalProvider,
|
||||||
|
data: externalProviderName,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String> updateGeoData({
|
||||||
|
required String geoType,
|
||||||
|
required String geoName,
|
||||||
|
}) {
|
||||||
|
return _invoke<String>(
|
||||||
|
method: ActionMethod.updateGeoData,
|
||||||
|
data: json.encode(
|
||||||
|
{
|
||||||
|
"geoType": geoType,
|
||||||
|
"geoName": geoName,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String> sideLoadExternalProvider({
|
||||||
|
required String providerName,
|
||||||
|
required String data,
|
||||||
|
}) {
|
||||||
|
return _invoke<String>(
|
||||||
|
method: ActionMethod.sideLoadExternalProvider,
|
||||||
|
data: json.encode({
|
||||||
|
"providerName": providerName,
|
||||||
|
"data": data,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String> updateExternalProvider(String providerName) {
|
||||||
|
return _invoke<String>(
|
||||||
|
method: ActionMethod.updateExternalProvider,
|
||||||
|
data: providerName,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureOr<String> getConnections() {
|
||||||
|
return _invoke<String>(
|
||||||
|
method: ActionMethod.getConnections,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> closeConnections() {
|
||||||
|
return _invoke<bool>(
|
||||||
|
method: ActionMethod.closeConnections,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> closeConnection(String id) {
|
||||||
|
return _invoke<bool>(
|
||||||
|
method: ActionMethod.closeConnection,
|
||||||
|
data: id,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureOr<String> getTotalTraffic(bool value) {
|
||||||
|
return _invoke<String>(
|
||||||
|
method: ActionMethod.getTotalTraffic,
|
||||||
|
data: value,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureOr<String> getTraffic(bool value) {
|
||||||
|
return _invoke<String>(
|
||||||
|
method: ActionMethod.getTraffic,
|
||||||
|
data: value,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
resetTraffic() {
|
||||||
|
_prueInvoke(method: ActionMethod.resetTraffic);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
startLog() {
|
||||||
|
_prueInvoke(method: ActionMethod.startLog);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
stopLog() {
|
||||||
|
_prueInvoke(method: ActionMethod.stopLog);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> startListener() {
|
||||||
|
return _invoke<bool>(
|
||||||
|
method: ActionMethod.startListener,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
stopListener() {
|
||||||
|
return _invoke<bool>(
|
||||||
|
method: ActionMethod.stopListener,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String> asyncTestDelay(String proxyName) {
|
||||||
|
final delayParams = {
|
||||||
|
"proxy-name": proxyName,
|
||||||
|
"timeout": httpTimeoutDuration.inMilliseconds,
|
||||||
|
};
|
||||||
|
return _invoke<String>(
|
||||||
|
method: ActionMethod.asyncTestDelay,
|
||||||
|
data: json.encode(delayParams),
|
||||||
|
timeout: Duration(
|
||||||
|
milliseconds: 6000,
|
||||||
|
),
|
||||||
|
onTimeout: () {
|
||||||
|
return json.encode(
|
||||||
|
Delay(
|
||||||
|
name: proxyName,
|
||||||
|
value: -1,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() async {
|
||||||
|
final server = await serverCompleter.future;
|
||||||
|
await server.close();
|
||||||
|
await _deleteSocketFile();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final clashService = ClashService();
|
final clashService = system.isDesktop ? ClashService() : null;
|
||||||
|
|||||||
@@ -1,33 +1,36 @@
|
|||||||
export 'path.dart';
|
|
||||||
export 'request.dart';
|
|
||||||
export 'preferences.dart';
|
|
||||||
export 'constant.dart';
|
|
||||||
export 'proxy.dart';
|
|
||||||
export 'other.dart';
|
|
||||||
export 'num.dart';
|
|
||||||
export 'navigation.dart';
|
|
||||||
export 'window.dart';
|
|
||||||
export 'system.dart';
|
|
||||||
export 'picker.dart';
|
|
||||||
export 'android.dart';
|
export 'android.dart';
|
||||||
export 'launch.dart';
|
|
||||||
export 'protocol.dart';
|
|
||||||
export 'datetime.dart';
|
|
||||||
export 'context.dart';
|
|
||||||
export 'link.dart';
|
|
||||||
export 'text.dart';
|
|
||||||
export 'color.dart';
|
|
||||||
export 'list.dart';
|
|
||||||
export 'string.dart';
|
|
||||||
export 'app_localizations.dart';
|
export 'app_localizations.dart';
|
||||||
|
export 'color.dart';
|
||||||
|
export 'constant.dart';
|
||||||
|
export 'context.dart';
|
||||||
|
export 'datetime.dart';
|
||||||
export 'function.dart';
|
export 'function.dart';
|
||||||
export 'package.dart';
|
export 'future.dart';
|
||||||
export 'measure.dart';
|
|
||||||
export 'windows.dart';
|
|
||||||
export 'iterable.dart';
|
|
||||||
export 'scroll.dart';
|
|
||||||
export 'icons.dart';
|
|
||||||
export 'http.dart';
|
export 'http.dart';
|
||||||
|
export 'icons.dart';
|
||||||
|
export 'iterable.dart';
|
||||||
export 'keyboard.dart';
|
export 'keyboard.dart';
|
||||||
export 'network.dart';
|
export 'launch.dart';
|
||||||
|
export 'link.dart';
|
||||||
|
export 'list.dart';
|
||||||
|
export 'lock.dart';
|
||||||
|
export 'measure.dart';
|
||||||
|
export 'navigation.dart';
|
||||||
export 'navigator.dart';
|
export 'navigator.dart';
|
||||||
|
export 'network.dart';
|
||||||
|
export 'num.dart';
|
||||||
|
export 'other.dart';
|
||||||
|
export 'package.dart';
|
||||||
|
export 'path.dart';
|
||||||
|
export 'picker.dart';
|
||||||
|
export 'preferences.dart';
|
||||||
|
export 'protocol.dart';
|
||||||
|
export 'proxy.dart';
|
||||||
|
export 'request.dart';
|
||||||
|
export 'scroll.dart';
|
||||||
|
export 'string.dart';
|
||||||
|
export 'system.dart';
|
||||||
|
export 'text.dart';
|
||||||
|
export 'tray.dart';
|
||||||
|
export 'window.dart';
|
||||||
|
export 'windows.dart';
|
||||||
|
|||||||
@@ -1,15 +1,20 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
import 'dart:math';
|
||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.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';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'system.dart';
|
|
||||||
|
|
||||||
const appName = "FlClash";
|
const appName = "FlClash";
|
||||||
|
const appHelperService = "FlClashHelperService";
|
||||||
const coreName = "clash.meta";
|
const coreName = "clash.meta";
|
||||||
const packageName = "com.follow.clash";
|
const packageName = "com.follow.clash";
|
||||||
|
final unixSocketPath = "/tmp/FlClashSocket_${Random().nextInt(10000)}.sock";
|
||||||
|
const helperPort = 47890;
|
||||||
|
const helperTag = "2024125";
|
||||||
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 animateDuration = Duration(milliseconds: 100);
|
||||||
@@ -21,7 +26,7 @@ const geoSiteFileName = "GeoSite.dat";
|
|||||||
final double kHeaderHeight = system.isDesktop
|
final double kHeaderHeight = system.isDesktop
|
||||||
? !Platform.isMacOS
|
? !Platform.isMacOS
|
||||||
? 40
|
? 40
|
||||||
: 26
|
: 28
|
||||||
: 0;
|
: 0;
|
||||||
const GeoXMap defaultGeoXMap = {
|
const GeoXMap defaultGeoXMap = {
|
||||||
"mmdb":
|
"mmdb":
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ class DAVClient {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
client.setConnectTimeout(8000);
|
client.setConnectTimeout(8000);
|
||||||
client.setSendTimeout(8000);
|
client.setSendTimeout(60000);
|
||||||
client.setReceiveTimeout(8000);
|
client.setReceiveTimeout(60000);
|
||||||
pingCompleter.complete(_ping());
|
pingCompleter.complete(_ping());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
42
lib/common/future.dart
Normal file
42
lib/common/future.dart
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
extension CompleterExt<T> on Completer<T> {
|
||||||
|
safeFuture({
|
||||||
|
Duration? timeout,
|
||||||
|
VoidCallback? onLast,
|
||||||
|
FutureOr<T> Function()? onTimeout,
|
||||||
|
required String functionName,
|
||||||
|
}) {
|
||||||
|
final realTimeout = timeout ?? const Duration(seconds: 6);
|
||||||
|
Timer(realTimeout + Duration(milliseconds: 1000), () {
|
||||||
|
if (onLast != null) {
|
||||||
|
onLast();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return future.withTimeout(
|
||||||
|
timeout: realTimeout,
|
||||||
|
functionName: functionName,
|
||||||
|
onTimeout: onTimeout,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension FutureExt<T> on Future<T> {
|
||||||
|
Future<T> withTimeout({
|
||||||
|
required Duration timeout,
|
||||||
|
required String functionName,
|
||||||
|
FutureOr<T> Function()? onTimeout,
|
||||||
|
}) {
|
||||||
|
return this.timeout(
|
||||||
|
timeout,
|
||||||
|
onTimeout: () async {
|
||||||
|
if (onTimeout != null) {
|
||||||
|
return onTimeout();
|
||||||
|
} else {
|
||||||
|
throw TimeoutException('$functionName timeout');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ import 'dart:io';
|
|||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
|
|
||||||
import '../state.dart';
|
import '../state.dart';
|
||||||
|
import 'constant.dart';
|
||||||
|
|
||||||
class FlClashHttpOverrides extends HttpOverrides {
|
class FlClashHttpOverrides extends HttpOverrides {
|
||||||
@override
|
@override
|
||||||
@@ -10,6 +11,9 @@ class FlClashHttpOverrides extends HttpOverrides {
|
|||||||
final client = super.createHttpClient(context);
|
final client = super.createHttpClient(context);
|
||||||
client.badCertificateCallback = (_, __, ___) => true;
|
client.badCertificateCallback = (_, __, ___) => true;
|
||||||
client.findProxy = (url) {
|
client.findProxy = (url) {
|
||||||
|
if ([localhost].contains(url.host)) {
|
||||||
|
return "DIRECT";
|
||||||
|
}
|
||||||
debugPrint("find $url");
|
debugPrint("find $url");
|
||||||
final appController = globalState.appController;
|
final appController = globalState.appController;
|
||||||
final port = appController.clashConfig.mixedPort;
|
final port = appController.clashConfig.mixedPort;
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:fl_clash/models/models.dart' hide Process;
|
|
||||||
|
import 'package:fl_clash/models/models.dart';
|
||||||
import 'package:launch_at_startup/launch_at_startup.dart';
|
import 'package:launch_at_startup/launch_at_startup.dart';
|
||||||
|
|
||||||
import 'constant.dart';
|
import 'constant.dart';
|
||||||
import 'system.dart';
|
import 'system.dart';
|
||||||
import 'windows.dart';
|
|
||||||
|
|
||||||
class AutoLaunch {
|
class AutoLaunch {
|
||||||
static AutoLaunch? _instance;
|
static AutoLaunch? _instance;
|
||||||
@@ -26,60 +26,16 @@ class AutoLaunch {
|
|||||||
return await launchAtStartup.isEnabled();
|
return await launchAtStartup.isEnabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> get windowsIsEnable async {
|
|
||||||
final res = await Process.run(
|
|
||||||
'schtasks',
|
|
||||||
['/Query', '/TN', appName, '/V', "/FO", "LIST"],
|
|
||||||
runInShell: true,
|
|
||||||
);
|
|
||||||
return res.stdout.toString().contains(Platform.resolvedExecutable);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<bool> enable() async {
|
Future<bool> enable() async {
|
||||||
if (Platform.isWindows) {
|
|
||||||
await windowsDisable();
|
|
||||||
}
|
|
||||||
return await launchAtStartup.enable();
|
return await launchAtStartup.enable();
|
||||||
}
|
}
|
||||||
|
|
||||||
windowsDisable() async {
|
|
||||||
final res = await Process.run(
|
|
||||||
'schtasks',
|
|
||||||
[
|
|
||||||
'/Delete',
|
|
||||||
'/TN',
|
|
||||||
appName,
|
|
||||||
'/F',
|
|
||||||
],
|
|
||||||
runInShell: true,
|
|
||||||
);
|
|
||||||
return res.exitCode == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<bool> windowsEnable() async {
|
|
||||||
await disable();
|
|
||||||
return await windows?.registerTask(appName) ?? false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<bool> disable() async {
|
Future<bool> disable() async {
|
||||||
return await launchAtStartup.disable();
|
return await launchAtStartup.disable();
|
||||||
}
|
}
|
||||||
|
|
||||||
updateStatus(AutoLaunchState state) async {
|
updateStatus(AutoLaunchState state) async {
|
||||||
final isAdminAutoLaunch = state.isAdminAutoLaunch;
|
|
||||||
final isAutoLaunch = state.isAutoLaunch;
|
final isAutoLaunch = state.isAutoLaunch;
|
||||||
if (Platform.isWindows && isAdminAutoLaunch) {
|
|
||||||
if (await windowsIsEnable == isAutoLaunch) return;
|
|
||||||
if (isAutoLaunch) {
|
|
||||||
final isEnable = await windowsEnable();
|
|
||||||
if (!isEnable) {
|
|
||||||
enable();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
windowsDisable();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (await isEnable == isAutoLaunch) return;
|
if (await isEnable == isAutoLaunch) return;
|
||||||
if (isAutoLaunch == true) {
|
if (isAutoLaunch == true) {
|
||||||
enable();
|
enable();
|
||||||
|
|||||||
30
lib/common/lock.dart
Normal file
30
lib/common/lock.dart
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:fl_clash/common/common.dart';
|
||||||
|
|
||||||
|
class SingleInstanceLock {
|
||||||
|
static SingleInstanceLock? _instance;
|
||||||
|
RandomAccessFile? _accessFile;
|
||||||
|
|
||||||
|
SingleInstanceLock._internal();
|
||||||
|
|
||||||
|
factory SingleInstanceLock() {
|
||||||
|
_instance ??= SingleInstanceLock._internal();
|
||||||
|
return _instance!;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> acquire() async {
|
||||||
|
try {
|
||||||
|
final lockFilePath = await appPath.getLockFilePath();
|
||||||
|
final lockFile = File(lockFilePath);
|
||||||
|
await lockFile.create();
|
||||||
|
_accessFile = await lockFile.open(mode: FileMode.write);
|
||||||
|
await _accessFile?.lock();
|
||||||
|
return true;
|
||||||
|
} catch (_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final singleInstanceLock = SingleInstanceLock();
|
||||||
@@ -7,9 +7,9 @@ import 'dart:ui';
|
|||||||
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:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:image/image.dart' as img;
|
||||||
import 'package:lpinyin/lpinyin.dart';
|
import 'package:lpinyin/lpinyin.dart';
|
||||||
import 'package:zxing2/qrcode.dart';
|
import 'package:zxing2/qrcode.dart';
|
||||||
import 'package:image/image.dart' as img;
|
|
||||||
|
|
||||||
class Other {
|
class Other {
|
||||||
Color? getDelayColor(int? delay) {
|
Color? getDelayColor(int? delay) {
|
||||||
@@ -19,6 +19,14 @@ class Other {
|
|||||||
return const Color(0xFFC57F0A);
|
return const Color(0xFFC57F0A);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String get id {
|
||||||
|
final timestamp = DateTime.now().microsecondsSinceEpoch;
|
||||||
|
final random = Random();
|
||||||
|
final randomStr =
|
||||||
|
String.fromCharCodes(List.generate(8, (_) => random.nextInt(26) + 97));
|
||||||
|
return "$timestamp$randomStr";
|
||||||
|
}
|
||||||
|
|
||||||
String getDateStringLast2(int value) {
|
String getDateStringLast2(int value) {
|
||||||
var valueRaw = "0$value";
|
var valueRaw = "0$value";
|
||||||
return valueRaw.substring(
|
return valueRaw.substring(
|
||||||
|
|||||||
@@ -13,14 +13,6 @@ class AppPath {
|
|||||||
Completer<Directory> tempDir = Completer();
|
Completer<Directory> tempDir = Completer();
|
||||||
late String appDirPath;
|
late String appDirPath;
|
||||||
|
|
||||||
// Future<Directory> _createDesktopCacheDir() async {
|
|
||||||
// final dir = Directory(path);
|
|
||||||
// if (await dir.exists()) {
|
|
||||||
// await dir.create(recursive: true);
|
|
||||||
// }
|
|
||||||
// return dir;
|
|
||||||
// }
|
|
||||||
|
|
||||||
AppPath._internal() {
|
AppPath._internal() {
|
||||||
appDirPath = join(dirname(Platform.resolvedExecutable));
|
appDirPath = join(dirname(Platform.resolvedExecutable));
|
||||||
getApplicationSupportDirectory().then((value) {
|
getApplicationSupportDirectory().then((value) {
|
||||||
@@ -32,15 +24,6 @@ class AppPath {
|
|||||||
getDownloadsDirectory().then((value) {
|
getDownloadsDirectory().then((value) {
|
||||||
downloadDir.complete(value);
|
downloadDir.complete(value);
|
||||||
});
|
});
|
||||||
// if (Platform.isAndroid) {
|
|
||||||
// getApplicationSupportDirectory().then((value) {
|
|
||||||
// cacheDir.complete(value);
|
|
||||||
// });
|
|
||||||
// } else {
|
|
||||||
// _createDesktopCacheDir().then((value) {
|
|
||||||
// cacheDir.complete(value);
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
factory AppPath() {
|
factory AppPath() {
|
||||||
@@ -48,6 +31,23 @@ class AppPath {
|
|||||||
return _instance!;
|
return _instance!;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String get executableExtension {
|
||||||
|
return Platform.isWindows ? ".exe" : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
String get executableDirPath {
|
||||||
|
final currentExecutablePath = Platform.resolvedExecutable;
|
||||||
|
return dirname(currentExecutablePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
String get corePath {
|
||||||
|
return join(executableDirPath, "FlClashCore$executableExtension");
|
||||||
|
}
|
||||||
|
|
||||||
|
String get helperPath {
|
||||||
|
return join(executableDirPath, "$appHelperService$executableExtension");
|
||||||
|
}
|
||||||
|
|
||||||
Future<String> getDownloadDirPath() async {
|
Future<String> getDownloadDirPath() async {
|
||||||
final directory = await downloadDir.future;
|
final directory = await downloadDir.future;
|
||||||
return directory.path;
|
return directory.path;
|
||||||
@@ -58,6 +58,11 @@ class AppPath {
|
|||||||
return directory.path;
|
return directory.path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<String> getLockFilePath() async {
|
||||||
|
final directory = await dataDir.future;
|
||||||
|
return join(directory.path, "FlClash.lock");
|
||||||
|
}
|
||||||
|
|
||||||
Future<String> getProfilesPath() async {
|
Future<String> getProfilesPath() async {
|
||||||
final directory = await dataDir.future;
|
final directory = await dataDir.future;
|
||||||
return join(directory.path, profilesDirectoryName);
|
return join(directory.path, profilesDirectoryName);
|
||||||
@@ -69,6 +74,12 @@ class AppPath {
|
|||||||
return join(directory, "$id.yaml");
|
return join(directory, "$id.yaml");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<String?> getProvidersPath(String? id) async {
|
||||||
|
if (id == null) return null;
|
||||||
|
final directory = await getProfilesPath();
|
||||||
|
return join(directory, "providers", id);
|
||||||
|
}
|
||||||
|
|
||||||
Future<String> get tempPath async {
|
Future<String> get tempPath async {
|
||||||
final directory = await tempDir.future;
|
final directory = await tempDir.future;
|
||||||
return directory.path;
|
return directory.path;
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
@@ -79,25 +81,96 @@ class Request {
|
|||||||
for (final source in _ipInfoSources.entries) {
|
for (final source in _ipInfoSources.entries) {
|
||||||
try {
|
try {
|
||||||
final response = await _dio
|
final response = await _dio
|
||||||
.get<Map<String, dynamic>>(
|
.get<Map<String, dynamic>>(source.key, cancelToken: cancelToken)
|
||||||
source.key,
|
.timeout(httpTimeoutDuration);
|
||||||
cancelToken: cancelToken,
|
if (response.statusCode != 200 || response.data == null) {
|
||||||
)
|
continue;
|
||||||
.timeout(
|
|
||||||
httpTimeoutDuration,
|
|
||||||
);
|
|
||||||
if (response.statusCode == 200 && response.data != null) {
|
|
||||||
return source.value(response.data!);
|
|
||||||
}
|
}
|
||||||
|
return source.value(response.data!);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (cancelToken?.isCancelled == true) {
|
if (e is DioException && e.type == DioExceptionType.cancel) {
|
||||||
throw "cancelled";
|
throw "cancelled";
|
||||||
}
|
}
|
||||||
continue;
|
debugPrint("checkIp error ===> $e");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<bool> pingHelper() async {
|
||||||
|
try {
|
||||||
|
final response = await _dio
|
||||||
|
.get(
|
||||||
|
"http://$localhost:$helperPort/ping",
|
||||||
|
options: Options(
|
||||||
|
responseType: ResponseType.plain,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.timeout(
|
||||||
|
const Duration(
|
||||||
|
milliseconds: 2000,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (response.statusCode != HttpStatus.ok) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return (response.data as String) == helperTag;
|
||||||
|
} catch (_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> startCoreByHelper(String arg) async {
|
||||||
|
try {
|
||||||
|
final response = await _dio
|
||||||
|
.post(
|
||||||
|
"http://$localhost:$helperPort/start",
|
||||||
|
data: json.encode({
|
||||||
|
"path": appPath.corePath,
|
||||||
|
"arg": arg,
|
||||||
|
}),
|
||||||
|
options: Options(
|
||||||
|
responseType: ResponseType.plain,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.timeout(
|
||||||
|
const Duration(
|
||||||
|
milliseconds: 2000,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (response.statusCode != HttpStatus.ok) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final data = response.data as String;
|
||||||
|
return data.isEmpty;
|
||||||
|
} catch (_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> stopCoreByHelper() async {
|
||||||
|
try {
|
||||||
|
final response = await _dio
|
||||||
|
.post(
|
||||||
|
"http://$localhost:$helperPort/stop",
|
||||||
|
options: Options(
|
||||||
|
responseType: ResponseType.plain,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.timeout(
|
||||||
|
const Duration(
|
||||||
|
milliseconds: 2000,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (response.statusCode != HttpStatus.ok) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final data = response.data as String;
|
||||||
|
return data.isEmpty;
|
||||||
|
} catch (_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final request = Request();
|
final request = Request();
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:device_info_plus/device_info_plus.dart';
|
import 'package:device_info_plus/device_info_plus.dart';
|
||||||
|
import 'package:fl_clash/common/common.dart';
|
||||||
|
import 'package:fl_clash/enum/enum.dart';
|
||||||
import 'package:fl_clash/plugins/app.dart';
|
import 'package:fl_clash/plugins/app.dart';
|
||||||
|
import 'package:fl_clash/state.dart';
|
||||||
|
import 'package:fl_clash/widgets/input.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
import 'window.dart';
|
|
||||||
|
|
||||||
class System {
|
class System {
|
||||||
static System? _instance;
|
static System? _instance;
|
||||||
|
|
||||||
@@ -19,12 +21,6 @@ class System {
|
|||||||
bool get isDesktop =>
|
bool get isDesktop =>
|
||||||
Platform.isWindows || Platform.isMacOS || Platform.isLinux;
|
Platform.isWindows || Platform.isMacOS || Platform.isLinux;
|
||||||
|
|
||||||
get isAdmin async {
|
|
||||||
if (!Platform.isWindows) return false;
|
|
||||||
final result = await Process.run('net', ['session'], runInShell: true);
|
|
||||||
return result.exitCode == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<int> get version async {
|
Future<int> get version async {
|
||||||
final deviceInfo = await DeviceInfoPlugin().deviceInfo;
|
final deviceInfo = await DeviceInfoPlugin().deviceInfo;
|
||||||
return switch (Platform.operatingSystem) {
|
return switch (Platform.operatingSystem) {
|
||||||
@@ -35,6 +31,73 @@ class System {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<bool> checkIsAdmin() async {
|
||||||
|
final corePath = appPath.corePath.replaceAll(' ', '\\\\ ');
|
||||||
|
if (Platform.isWindows) {
|
||||||
|
final result = await windows?.checkService();
|
||||||
|
return result == WindowsHelperServiceStatus.running;
|
||||||
|
} else if (Platform.isMacOS) {
|
||||||
|
final result = await Process.run('stat', ['-f', '%Su:%Sg %Sp', corePath]);
|
||||||
|
final output = result.stdout.trim();
|
||||||
|
if (output.startsWith('root:admin') && output.contains('rws')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
} else if (Platform.isLinux) {
|
||||||
|
final result = await Process.run('stat', ['-c', '%U:%G %A', corePath]);
|
||||||
|
final output = result.stdout.trim();
|
||||||
|
if (output.startsWith('root:') && output.contains('rwx')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<AuthorizeCode> authorizeCore() async {
|
||||||
|
final corePath = appPath.corePath.replaceAll(' ', '\\\\ ');
|
||||||
|
final isAdmin = await checkIsAdmin();
|
||||||
|
if (isAdmin) {
|
||||||
|
return AuthorizeCode.none;
|
||||||
|
}
|
||||||
|
if (Platform.isWindows) {
|
||||||
|
final result = await windows?.registerService();
|
||||||
|
if (result == true) {
|
||||||
|
return AuthorizeCode.success;
|
||||||
|
}
|
||||||
|
return AuthorizeCode.error;
|
||||||
|
} else if (Platform.isMacOS) {
|
||||||
|
final shell = 'chown root:admin $corePath; chmod +sx $corePath';
|
||||||
|
final arguments = [
|
||||||
|
"-e",
|
||||||
|
'do shell script "$shell" with administrator privileges',
|
||||||
|
];
|
||||||
|
final result = await Process.run("osascript", arguments);
|
||||||
|
if (result.exitCode != 0) {
|
||||||
|
return AuthorizeCode.error;
|
||||||
|
}
|
||||||
|
return AuthorizeCode.success;
|
||||||
|
} else if (Platform.isLinux) {
|
||||||
|
final shell = Platform.environment['SHELL'] ?? 'bash';
|
||||||
|
final password = await globalState.showCommonDialog<String>(
|
||||||
|
child: InputDialog(
|
||||||
|
title: appLocalizations.pleaseInputAdminPassword,
|
||||||
|
value: '',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
final arguments = [
|
||||||
|
"-c",
|
||||||
|
'echo "$password" | sudo -S chown root:root "$corePath" && echo "$password" | sudo -S chmod +sx "$corePath"'
|
||||||
|
];
|
||||||
|
final result = await Process.run(shell, arguments);
|
||||||
|
if (result.exitCode != 0) {
|
||||||
|
return AuthorizeCode.error;
|
||||||
|
}
|
||||||
|
return AuthorizeCode.success;
|
||||||
|
}
|
||||||
|
return AuthorizeCode.error;
|
||||||
|
}
|
||||||
|
|
||||||
back() async {
|
back() async {
|
||||||
await app?.moveTaskToBack();
|
await app?.moveTaskToBack();
|
||||||
await window?.hide();
|
await window?.hide();
|
||||||
|
|||||||
193
lib/common/tray.dart
Normal file
193
lib/common/tray.dart
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:fl_clash/enum/enum.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';
|
||||||
|
import 'package:tray_manager/tray_manager.dart';
|
||||||
|
|
||||||
|
import 'app_localizations.dart';
|
||||||
|
import 'constant.dart';
|
||||||
|
import 'other.dart';
|
||||||
|
import 'window.dart';
|
||||||
|
|
||||||
|
class Tray {
|
||||||
|
Future _updateSystemTray({
|
||||||
|
required Brightness? brightness,
|
||||||
|
bool force = false,
|
||||||
|
}) async {
|
||||||
|
if (Platform.isAndroid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (Platform.isLinux || force) {
|
||||||
|
await trayManager.destroy();
|
||||||
|
}
|
||||||
|
await trayManager.setIcon(
|
||||||
|
other.getTrayIconPath(
|
||||||
|
brightness: brightness ??
|
||||||
|
WidgetsBinding.instance.platformDispatcher.platformBrightness,
|
||||||
|
),
|
||||||
|
isTemplate: true,
|
||||||
|
);
|
||||||
|
if (!Platform.isLinux) {
|
||||||
|
await trayManager.setToolTip(
|
||||||
|
appName,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
update({
|
||||||
|
required AppState appState,
|
||||||
|
required AppFlowingState appFlowingState,
|
||||||
|
required Config config,
|
||||||
|
required ClashConfig clashConfig,
|
||||||
|
bool focus = false,
|
||||||
|
}) async {
|
||||||
|
if (Platform.isAndroid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!Platform.isLinux) {
|
||||||
|
await _updateSystemTray(
|
||||||
|
brightness: appState.brightness,
|
||||||
|
force: focus,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
List<MenuItem> menuItems = [];
|
||||||
|
final showMenuItem = MenuItem(
|
||||||
|
label: appLocalizations.show,
|
||||||
|
onClick: (_) {
|
||||||
|
window?.show();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
menuItems.add(showMenuItem);
|
||||||
|
final startMenuItem = MenuItem.checkbox(
|
||||||
|
label: appFlowingState.isStart
|
||||||
|
? appLocalizations.stop
|
||||||
|
: appLocalizations.start,
|
||||||
|
onClick: (_) async {
|
||||||
|
globalState.appController.updateStart();
|
||||||
|
},
|
||||||
|
checked: false,
|
||||||
|
);
|
||||||
|
menuItems.add(startMenuItem);
|
||||||
|
menuItems.add(MenuItem.separator());
|
||||||
|
for (final mode in Mode.values) {
|
||||||
|
menuItems.add(
|
||||||
|
MenuItem.checkbox(
|
||||||
|
label: Intl.message(mode.name),
|
||||||
|
onClick: (_) {
|
||||||
|
globalState.appController.clashConfig.mode = mode;
|
||||||
|
},
|
||||||
|
checked: mode == clashConfig.mode,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
menuItems.add(MenuItem.separator());
|
||||||
|
if (!Platform.isWindows) {
|
||||||
|
final groups = appState.currentGroups;
|
||||||
|
for (final group in groups) {
|
||||||
|
List<MenuItem> subMenuItems = [];
|
||||||
|
for (final proxy in group.all) {
|
||||||
|
subMenuItems.add(
|
||||||
|
MenuItem.checkbox(
|
||||||
|
label: proxy.name,
|
||||||
|
checked: appState.selectedMap[group.name] == proxy.name,
|
||||||
|
onClick: (_) {
|
||||||
|
final appController = globalState.appController;
|
||||||
|
appController.config.updateCurrentSelectedMap(
|
||||||
|
group.name,
|
||||||
|
proxy.name,
|
||||||
|
);
|
||||||
|
appController.changeProxy(
|
||||||
|
groupName: group.name,
|
||||||
|
proxyName: proxy.name,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
menuItems.add(
|
||||||
|
MenuItem.submenu(
|
||||||
|
label: group.name,
|
||||||
|
submenu: Menu(
|
||||||
|
items: subMenuItems,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (groups.isNotEmpty) {
|
||||||
|
menuItems.add(MenuItem.separator());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (appFlowingState.isStart) {
|
||||||
|
menuItems.add(
|
||||||
|
MenuItem.checkbox(
|
||||||
|
label: appLocalizations.tun,
|
||||||
|
onClick: (_) {
|
||||||
|
globalState.appController.updateTun();
|
||||||
|
},
|
||||||
|
checked: clashConfig.tun.enable,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
menuItems.add(
|
||||||
|
MenuItem.checkbox(
|
||||||
|
label: appLocalizations.systemProxy,
|
||||||
|
onClick: (_) {
|
||||||
|
globalState.appController.updateSystemProxy();
|
||||||
|
},
|
||||||
|
checked: config.networkProps.systemProxy,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
menuItems.add(MenuItem.separator());
|
||||||
|
}
|
||||||
|
final autoStartMenuItem = MenuItem.checkbox(
|
||||||
|
label: appLocalizations.autoLaunch,
|
||||||
|
onClick: (_) async {
|
||||||
|
globalState.appController.updateAutoLaunch();
|
||||||
|
},
|
||||||
|
checked: config.appSetting.autoLaunch,
|
||||||
|
);
|
||||||
|
final copyEnvVarMenuItem = MenuItem(
|
||||||
|
label: appLocalizations.copyEnvVar,
|
||||||
|
onClick: (_) async {
|
||||||
|
await _copyEnv(clashConfig.mixedPort);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
menuItems.add(autoStartMenuItem);
|
||||||
|
menuItems.add(copyEnvVarMenuItem);
|
||||||
|
menuItems.add(MenuItem.separator());
|
||||||
|
final exitMenuItem = MenuItem(
|
||||||
|
label: appLocalizations.exit,
|
||||||
|
onClick: (_) async {
|
||||||
|
await globalState.appController.handleExit();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
menuItems.add(exitMenuItem);
|
||||||
|
final menu = Menu(items: menuItems);
|
||||||
|
await trayManager.setContextMenu(menu);
|
||||||
|
if (Platform.isLinux) {
|
||||||
|
await _updateSystemTray(
|
||||||
|
brightness: appState.brightness,
|
||||||
|
force: focus,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _copyEnv(int port) async {
|
||||||
|
final url = "http://127.0.0.1:$port";
|
||||||
|
|
||||||
|
final cmdline = Platform.isWindows
|
||||||
|
? "set \$env:all_proxy=$url"
|
||||||
|
: "export all_proxy=$url";
|
||||||
|
|
||||||
|
await Clipboard.setData(
|
||||||
|
ClipboardData(
|
||||||
|
text: cmdline,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final tray = Tray();
|
||||||
@@ -4,12 +4,14 @@ import 'package:fl_clash/common/common.dart';
|
|||||||
import 'package:fl_clash/models/config.dart';
|
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';
|
|
||||||
|
|
||||||
class Window {
|
class Window {
|
||||||
init(WindowProps props, int version) async {
|
init(WindowProps props, int version) async {
|
||||||
|
final acquire = await singleInstanceLock.acquire();
|
||||||
|
if (!acquire) {
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
if (Platform.isWindows) {
|
if (Platform.isWindows) {
|
||||||
await WindowsSingleInstance.ensureSingleInstance([], "FlClash");
|
|
||||||
protocol.register("clash");
|
protocol.register("clash");
|
||||||
protocol.register("clashmeta");
|
protocol.register("clashmeta");
|
||||||
protocol.register("flclash");
|
protocol.register("flclash");
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import 'dart:ffi';
|
import 'dart:ffi';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:ffi/ffi.dart';
|
import 'package:ffi/ffi.dart';
|
||||||
import 'package:fl_clash/common/common.dart';
|
import 'package:fl_clash/common/common.dart';
|
||||||
|
import 'package:fl_clash/enum/enum.dart';
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:path/path.dart';
|
import 'package:path/path.dart';
|
||||||
|
|
||||||
class Windows {
|
class Windows {
|
||||||
@@ -51,12 +54,84 @@ class Windows {
|
|||||||
calloc.free(argumentsPtr);
|
calloc.free(argumentsPtr);
|
||||||
calloc.free(operationPtr);
|
calloc.free(operationPtr);
|
||||||
|
|
||||||
if (result <= 32) {
|
debugPrint("[Windows] runas: $command $arguments resultCode:$result");
|
||||||
|
|
||||||
|
if (result < 42) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_killProcess(int port) async {
|
||||||
|
final result = await Process.run('netstat', ['-ano']);
|
||||||
|
final lines = result.stdout.toString().trim().split('\n');
|
||||||
|
for (final line in lines) {
|
||||||
|
if (!line.contains(":$port") || !line.contains("LISTENING")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final parts = line.trim().split(RegExp(r'\s+'));
|
||||||
|
final pid = int.tryParse(parts.last);
|
||||||
|
if (pid != null) {
|
||||||
|
await Process.run('taskkill', ['/PID', pid.toString(), '/F']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<WindowsHelperServiceStatus> checkService() async {
|
||||||
|
// final qcResult = await Process.run('sc', ['qc', appHelperService]);
|
||||||
|
// final qcOutput = qcResult.stdout.toString();
|
||||||
|
// if (qcResult.exitCode != 0 || !qcOutput.contains(appPath.helperPath)) {
|
||||||
|
// return WindowsHelperServiceStatus.none;
|
||||||
|
// }
|
||||||
|
final result = await Process.run('sc', ['query', appHelperService]);
|
||||||
|
if(result.exitCode != 0){
|
||||||
|
return WindowsHelperServiceStatus.none;
|
||||||
|
}
|
||||||
|
final output = result.stdout.toString();
|
||||||
|
if (output.contains("RUNNING") && await request.pingHelper()) {
|
||||||
|
return WindowsHelperServiceStatus.running;
|
||||||
|
}
|
||||||
|
return WindowsHelperServiceStatus.presence;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> registerService() async {
|
||||||
|
final status = await checkService();
|
||||||
|
|
||||||
|
if (status == WindowsHelperServiceStatus.running) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
await _killProcess(helperPort);
|
||||||
|
|
||||||
|
final command = [
|
||||||
|
"/c",
|
||||||
|
if (status == WindowsHelperServiceStatus.presence) ...[
|
||||||
|
"sc",
|
||||||
|
"delete",
|
||||||
|
appHelperService,
|
||||||
|
"/force",
|
||||||
|
"&&",
|
||||||
|
],
|
||||||
|
"sc",
|
||||||
|
"create",
|
||||||
|
appHelperService,
|
||||||
|
'binPath= "${appPath.helperPath}"',
|
||||||
|
'start= auto',
|
||||||
|
"&&",
|
||||||
|
"sc",
|
||||||
|
"start",
|
||||||
|
appHelperService,
|
||||||
|
].join(" ");
|
||||||
|
|
||||||
|
final res = runas("cmd.exe", command);
|
||||||
|
|
||||||
|
await Future.delayed(
|
||||||
|
Duration(milliseconds: 300),
|
||||||
|
);
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
Future<bool> registerTask(String appName) async {
|
Future<bool> registerTask(String appName) async {
|
||||||
final taskXml = '''
|
final taskXml = '''
|
||||||
<?xml version="1.0" encoding="UTF-16"?>
|
<?xml version="1.0" encoding="UTF-16"?>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import 'dart:isolate';
|
|||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:archive/archive.dart';
|
import 'package:archive/archive.dart';
|
||||||
|
import 'package:fl_clash/clash/clash.dart';
|
||||||
import 'package:fl_clash/common/archive.dart';
|
import 'package:fl_clash/common/archive.dart';
|
||||||
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';
|
||||||
@@ -13,7 +14,6 @@ import 'package:path/path.dart';
|
|||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
import 'clash/core.dart';
|
|
||||||
import 'common/common.dart';
|
import 'common/common.dart';
|
||||||
import 'models/models.dart';
|
import 'models/models.dart';
|
||||||
|
|
||||||
@@ -28,6 +28,7 @@ class AppController {
|
|||||||
late Function addCheckIpNumDebounce;
|
late Function addCheckIpNumDebounce;
|
||||||
late Function applyProfileDebounce;
|
late Function applyProfileDebounce;
|
||||||
late Function savePreferencesDebounce;
|
late Function savePreferencesDebounce;
|
||||||
|
late Function changeProxyDebounce;
|
||||||
|
|
||||||
AppController(this.context) {
|
AppController(this.context) {
|
||||||
appState = context.read<AppState>();
|
appState = context.read<AppState>();
|
||||||
@@ -43,6 +44,13 @@ class AppController {
|
|||||||
applyProfileDebounce = debounce<Function()>(() async {
|
applyProfileDebounce = debounce<Function()>(() async {
|
||||||
await applyProfile(isPrue: true);
|
await applyProfile(isPrue: true);
|
||||||
});
|
});
|
||||||
|
changeProxyDebounce = debounce((String groupName, String proxyName) async {
|
||||||
|
await changeProxy(
|
||||||
|
groupName: groupName,
|
||||||
|
proxyName: proxyName,
|
||||||
|
);
|
||||||
|
await updateGroups();
|
||||||
|
});
|
||||||
addCheckIpNumDebounce = debounce(() {
|
addCheckIpNumDebounce = debounce(() {
|
||||||
appState.checkIpNum++;
|
appState.checkIpNum++;
|
||||||
});
|
});
|
||||||
@@ -51,6 +59,14 @@ class AppController {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
restartCore() async {
|
||||||
|
await globalState.restartCore(
|
||||||
|
appState: appState,
|
||||||
|
clashConfig: clashConfig,
|
||||||
|
config: config,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
updateStatus(bool isStart) async {
|
updateStatus(bool isStart) async {
|
||||||
if (isStart) {
|
if (isStart) {
|
||||||
await globalState.handleStart();
|
await globalState.handleStart();
|
||||||
@@ -60,23 +76,31 @@ class AppController {
|
|||||||
updateRunTime,
|
updateRunTime,
|
||||||
updateTraffic,
|
updateTraffic,
|
||||||
];
|
];
|
||||||
if (!Platform.isAndroid) {
|
final currentLastModified =
|
||||||
applyProfileDebounce();
|
await config.getCurrentProfile()?.profileLastModified;
|
||||||
|
if (currentLastModified == null ||
|
||||||
|
globalState.lastProfileModified == null) {
|
||||||
|
addCheckIpNumDebounce();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
if (currentLastModified <= (globalState.lastProfileModified ?? 0)) {
|
||||||
|
addCheckIpNumDebounce();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
applyProfileDebounce();
|
||||||
} else {
|
} else {
|
||||||
await globalState.handleStop();
|
await globalState.handleStop();
|
||||||
clashCore.resetTraffic();
|
await clashCore.resetTraffic();
|
||||||
appFlowingState.traffics = [];
|
appFlowingState.traffics = [];
|
||||||
appFlowingState.totalTraffic = Traffic();
|
appFlowingState.totalTraffic = Traffic();
|
||||||
appFlowingState.runTime = null;
|
appFlowingState.runTime = null;
|
||||||
|
await Future.delayed(
|
||||||
|
Duration(milliseconds: 300),
|
||||||
|
);
|
||||||
addCheckIpNumDebounce();
|
addCheckIpNumDebounce();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateCoreVersionInfo() {
|
|
||||||
globalState.updateCoreVersionInfo(appState);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateRunTime() {
|
updateRunTime() {
|
||||||
final startTime = globalState.startTime;
|
final startTime = globalState.startTime;
|
||||||
if (startTime != null) {
|
if (startTime != null) {
|
||||||
@@ -90,6 +114,7 @@ class AppController {
|
|||||||
|
|
||||||
updateTraffic() {
|
updateTraffic() {
|
||||||
globalState.updateTraffic(
|
globalState.updateTraffic(
|
||||||
|
config: config,
|
||||||
appFlowingState: appFlowingState,
|
appFlowingState: appFlowingState,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -102,7 +127,7 @@ class AppController {
|
|||||||
|
|
||||||
deleteProfile(String id) async {
|
deleteProfile(String id) async {
|
||||||
config.deleteProfileById(id);
|
config.deleteProfileById(id);
|
||||||
clashCore.clearEffect(id);
|
clearEffect(id);
|
||||||
if (config.currentProfileId == id) {
|
if (config.currentProfileId == id) {
|
||||||
if (config.profiles.isNotEmpty) {
|
if (config.profiles.isNotEmpty) {
|
||||||
final updateId = config.profiles.first.id;
|
final updateId = config.profiles.first.id;
|
||||||
@@ -130,6 +155,7 @@ class AppController {
|
|||||||
if (commonScaffoldState?.mounted != true) return;
|
if (commonScaffoldState?.mounted != true) return;
|
||||||
await commonScaffoldState?.loadingRun(() async {
|
await commonScaffoldState?.loadingRun(() async {
|
||||||
await globalState.updateClashConfig(
|
await globalState.updateClashConfig(
|
||||||
|
appState: appState,
|
||||||
clashConfig: clashConfig,
|
clashConfig: clashConfig,
|
||||||
config: config,
|
config: config,
|
||||||
isPatch: isPatch,
|
isPatch: isPatch,
|
||||||
@@ -213,8 +239,8 @@ class AppController {
|
|||||||
changeProxy({
|
changeProxy({
|
||||||
required String groupName,
|
required String groupName,
|
||||||
required String proxyName,
|
required String proxyName,
|
||||||
}) {
|
}) async {
|
||||||
globalState.changeProxy(
|
await globalState.changeProxy(
|
||||||
config: config,
|
config: config,
|
||||||
groupName: groupName,
|
groupName: groupName,
|
||||||
proxyName: proxyName,
|
proxyName: proxyName,
|
||||||
@@ -234,22 +260,16 @@ class AppController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleExit() async {
|
handleExit() async {
|
||||||
|
try {
|
||||||
await updateStatus(false);
|
await updateStatus(false);
|
||||||
|
await clashCore.shutdown();
|
||||||
|
await clashService?.destroy();
|
||||||
await proxy?.stopProxy();
|
await proxy?.stopProxy();
|
||||||
await savePreferences();
|
await savePreferences();
|
||||||
clashCore.shutdown();
|
} catch (_) {}
|
||||||
system.exit();
|
system.exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
updateLogStatus() {
|
|
||||||
if (config.appSetting.openLogs) {
|
|
||||||
clashCore.startLog();
|
|
||||||
} else {
|
|
||||||
clashCore.stopLog();
|
|
||||||
appFlowingState.logs = [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
autoCheckUpdate() async {
|
autoCheckUpdate() async {
|
||||||
if (!config.appSetting.autoCheckUpdate) return;
|
if (!config.appSetting.autoCheckUpdate) return;
|
||||||
final res = await request.checkForUpdate();
|
final res = await request.checkForUpdate();
|
||||||
@@ -304,10 +324,20 @@ class AppController {
|
|||||||
if (!isDisclaimerAccepted) {
|
if (!isDisclaimerAccepted) {
|
||||||
handleExit();
|
handleExit();
|
||||||
}
|
}
|
||||||
updateLogStatus();
|
|
||||||
if (!config.appSetting.silentLaunch) {
|
if (!config.appSetting.silentLaunch) {
|
||||||
window?.show();
|
window?.show();
|
||||||
}
|
}
|
||||||
|
await globalState.initCore(
|
||||||
|
appState: appState,
|
||||||
|
clashConfig: clashConfig,
|
||||||
|
config: config,
|
||||||
|
);
|
||||||
|
await _initStatus();
|
||||||
|
autoUpdateProfiles();
|
||||||
|
autoCheckUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
_initStatus() async {
|
||||||
if (Platform.isAndroid) {
|
if (Platform.isAndroid) {
|
||||||
globalState.updateStartTime();
|
globalState.updateStartTime();
|
||||||
}
|
}
|
||||||
@@ -316,8 +346,6 @@ class AppController {
|
|||||||
} else {
|
} else {
|
||||||
await updateStatus(config.appSetting.autoRun);
|
await updateStatus(config.appSetting.autoRun);
|
||||||
}
|
}
|
||||||
autoUpdateProfiles();
|
|
||||||
autoCheckUpdate();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setDelay(Delay delay) {
|
setDelay(Delay delay) {
|
||||||
@@ -525,6 +553,19 @@ class AppController {
|
|||||||
'';
|
'';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clearEffect(String profileId) async {
|
||||||
|
final profilePath = await appPath.getProfilePath(profileId);
|
||||||
|
final providersPath = await appPath.getProvidersPath(profileId);
|
||||||
|
return await Isolate.run(() async {
|
||||||
|
if (profilePath != null) {
|
||||||
|
await File(profilePath).delete(recursive: true);
|
||||||
|
}
|
||||||
|
if (providersPath != null) {
|
||||||
|
await File(providersPath).delete(recursive: true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
updateTun() {
|
updateTun() {
|
||||||
clashConfig.tun = clashConfig.tun.copyWith(
|
clashConfig.tun = clashConfig.tun.copyWith(
|
||||||
enable: !clashConfig.tun.enable,
|
enable: !clashConfig.tun.enable,
|
||||||
@@ -547,12 +588,6 @@ class AppController {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateAdminAutoLaunch() {
|
|
||||||
config.appSetting = config.appSetting.copyWith(
|
|
||||||
adminAutoLaunch: !config.appSetting.adminAutoLaunch,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateVisible() async {
|
updateVisible() async {
|
||||||
final visible = await window?.isVisible();
|
final visible = await window?.isVisible();
|
||||||
if (visible != null && !visible) {
|
if (visible != null && !visible) {
|
||||||
@@ -602,7 +637,7 @@ class AppController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateTray([bool focus = false]) async {
|
updateTray([bool focus = false]) async {
|
||||||
globalState.updateTray(
|
tray.update(
|
||||||
appState: appState,
|
appState: appState,
|
||||||
appFlowingState: appFlowingState,
|
appFlowingState: appFlowingState,
|
||||||
config: config,
|
config: config,
|
||||||
|
|||||||
@@ -178,3 +178,39 @@ enum RouteMode {
|
|||||||
bypassPrivate,
|
bypassPrivate,
|
||||||
config,
|
config,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum ActionMethod {
|
||||||
|
message,
|
||||||
|
initClash,
|
||||||
|
getIsInit,
|
||||||
|
forceGc,
|
||||||
|
shutdown,
|
||||||
|
validateConfig,
|
||||||
|
updateConfig,
|
||||||
|
getProxies,
|
||||||
|
changeProxy,
|
||||||
|
getTraffic,
|
||||||
|
getTotalTraffic,
|
||||||
|
resetTraffic,
|
||||||
|
asyncTestDelay,
|
||||||
|
getConnections,
|
||||||
|
closeConnections,
|
||||||
|
closeConnection,
|
||||||
|
getExternalProviders,
|
||||||
|
getExternalProvider,
|
||||||
|
updateGeoData,
|
||||||
|
updateExternalProvider,
|
||||||
|
sideLoadExternalProvider,
|
||||||
|
startLog,
|
||||||
|
stopLog,
|
||||||
|
startListener,
|
||||||
|
stopListener,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum AuthorizeCode { none, success, error }
|
||||||
|
|
||||||
|
enum WindowsHelperServiceStatus {
|
||||||
|
none,
|
||||||
|
presence,
|
||||||
|
running,
|
||||||
|
}
|
||||||
|
|||||||
@@ -60,32 +60,6 @@ class UsageSwitch extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class AdminAutoLaunchItem extends StatelessWidget {
|
|
||||||
const AdminAutoLaunchItem({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Selector<Config, bool>(
|
|
||||||
selector: (_, config) => config.appSetting.adminAutoLaunch,
|
|
||||||
builder: (_, adminAutoLaunch, __) {
|
|
||||||
return ListItem.switchItem(
|
|
||||||
title: Text(appLocalizations.adminAutoLaunch),
|
|
||||||
subtitle: Text(appLocalizations.adminAutoLaunchDesc),
|
|
||||||
delegate: SwitchDelegate(
|
|
||||||
value: adminAutoLaunch,
|
|
||||||
onChanged: (bool value) async {
|
|
||||||
final config = globalState.appController.config;
|
|
||||||
config.appSetting = config.appSetting.copyWith(
|
|
||||||
adminAutoLaunch: value,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ApplicationSettingFragment extends StatelessWidget {
|
class ApplicationSettingFragment extends StatelessWidget {
|
||||||
const ApplicationSettingFragment({super.key});
|
const ApplicationSettingFragment({super.key});
|
||||||
|
|
||||||
@@ -134,8 +108,6 @@ class ApplicationSettingFragment extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
if(Platform.isWindows)
|
|
||||||
const AdminAutoLaunchItem(),
|
|
||||||
if (system.isDesktop)
|
if (system.isDesktop)
|
||||||
Selector<Config, bool>(
|
Selector<Config, bool>(
|
||||||
selector: (_, config) => config.appSetting.silentLaunch,
|
selector: (_, config) => config.appSetting.silentLaunch,
|
||||||
|
|||||||
@@ -27,18 +27,19 @@ class _ConnectionsFragmentState extends State<ConnectionsFragment> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||||
connectionsNotifier.value = connectionsNotifier.value
|
connectionsNotifier.value = connectionsNotifier.value.copyWith(
|
||||||
.copyWith(connections: clashCore.getConnections());
|
connections: await 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: 1),
|
const Duration(seconds: 1),
|
||||||
(timer) {
|
(timer) async {
|
||||||
connectionsNotifier.value = connectionsNotifier.value.copyWith(
|
connectionsNotifier.value = connectionsNotifier.value.copyWith(
|
||||||
connections: clashCore.getConnections(),
|
connections: await clashCore.getConnections(),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -66,10 +67,10 @@ class _ConnectionsFragmentState extends State<ConnectionsFragment> {
|
|||||||
width: 8,
|
width: 8,
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () {
|
onPressed: () async {
|
||||||
clashCore.closeConnections();
|
clashCore.closeConnections();
|
||||||
connectionsNotifier.value = connectionsNotifier.value.copyWith(
|
connectionsNotifier.value = connectionsNotifier.value.copyWith(
|
||||||
connections: clashCore.getConnections(),
|
connections: await clashCore.getConnections(),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.delete_sweep_outlined),
|
icon: const Icon(Icons.delete_sweep_outlined),
|
||||||
@@ -99,10 +100,11 @@ class _ConnectionsFragmentState extends State<ConnectionsFragment> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_handleBlockConnection(String id) {
|
_handleBlockConnection(String id) async {
|
||||||
clashCore.closeConnection(id);
|
clashCore.closeConnection(id);
|
||||||
connectionsNotifier.value = connectionsNotifier.value
|
connectionsNotifier.value = connectionsNotifier.value.copyWith(
|
||||||
.copyWith(connections: clashCore.getConnections());
|
connections: await clashCore.getConnections(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -239,10 +241,10 @@ class ConnectionsSearchDelegate extends SearchDelegate {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_handleBlockConnection(String id) {
|
_handleBlockConnection(String id) async {
|
||||||
clashCore.closeConnection(id);
|
clashCore.closeConnection(id);
|
||||||
connectionsNotifier.value = connectionsNotifier.value.copyWith(
|
connectionsNotifier.value = connectionsNotifier.value.copyWith(
|
||||||
connections: clashCore.getConnections(),
|
connections: await clashCore.getConnections(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import 'dart:io';
|
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:fl_clash/common/common.dart';
|
import 'package:fl_clash/common/common.dart';
|
||||||
@@ -68,7 +67,6 @@ class _DashboardFragmentState extends State<DashboardFragment> {
|
|||||||
// child: const VPNSwitch(),
|
// child: const VPNSwitch(),
|
||||||
// ),
|
// ),
|
||||||
if (system.isDesktop) ...[
|
if (system.isDesktop) ...[
|
||||||
if (Platform.isWindows)
|
|
||||||
GridItem(
|
GridItem(
|
||||||
crossAxisCellCount: switchCount,
|
crossAxisCellCount: switchCount,
|
||||||
child: const TUNButton(),
|
child: const TUNButton(),
|
||||||
|
|||||||
@@ -9,6 +9,13 @@ 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';
|
||||||
|
|
||||||
|
final networkDetectionState = ValueNotifier<NetworkDetectionState>(
|
||||||
|
const NetworkDetectionState(
|
||||||
|
isTesting: true,
|
||||||
|
ipInfo: null,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
class NetworkDetection extends StatefulWidget {
|
class NetworkDetection extends StatefulWidget {
|
||||||
const NetworkDetection({super.key});
|
const NetworkDetection({super.key});
|
||||||
|
|
||||||
@@ -17,12 +24,6 @@ class NetworkDetection extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _NetworkDetectionState extends State<NetworkDetection> {
|
class _NetworkDetectionState extends State<NetworkDetection> {
|
||||||
final networkDetectionState = ValueNotifier<NetworkDetectionState>(
|
|
||||||
const NetworkDetectionState(
|
|
||||||
isTesting: true,
|
|
||||||
ipInfo: null,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
bool? _preIsStart;
|
bool? _preIsStart;
|
||||||
Function? _checkIpDebounce;
|
Function? _checkIpDebounce;
|
||||||
Timer? _setTimeoutTimer;
|
Timer? _setTimeoutTimer;
|
||||||
@@ -55,19 +56,22 @@ class _NetworkDetectionState extends State<NetworkDetection> {
|
|||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_setTimeoutTimer = Timer(const Duration(milliseconds: 2000), () {
|
_clearSetTimeoutTimer();
|
||||||
|
_setTimeoutTimer = Timer(const Duration(milliseconds: 300), () {
|
||||||
networkDetectionState.value = networkDetectionState.value.copyWith(
|
networkDetectionState.value = networkDetectionState.value.copyWith(
|
||||||
isTesting: false,
|
isTesting: false,
|
||||||
ipInfo: null,
|
ipInfo: null,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
} catch (_) {
|
} catch (e) {
|
||||||
|
if (e.toString() == "cancelled") {
|
||||||
networkDetectionState.value = networkDetectionState.value.copyWith(
|
networkDetectionState.value = networkDetectionState.value.copyWith(
|
||||||
isTesting: true,
|
isTesting: true,
|
||||||
ipInfo: null,
|
ipInfo: null,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_clearSetTimeoutTimer() {
|
_clearSetTimeoutTimer() {
|
||||||
if (_setTimeoutTimer != null) {
|
if (_setTimeoutTimer != null) {
|
||||||
@@ -92,9 +96,8 @@ class _NetworkDetectionState extends State<NetworkDetection> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
dispose() {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
networkDetectionState.dispose();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String countryCodeToEmoji(String countryCode) {
|
String countryCodeToEmoji(String countryCode) {
|
||||||
@@ -156,7 +159,8 @@ class _NetworkDetectionState extends State<NetworkDetection> {
|
|||||||
.textTheme
|
.textTheme
|
||||||
.titleLarge
|
.titleLarge
|
||||||
?.copyWith(
|
?.copyWith(
|
||||||
fontFamily: FontFamily.twEmoji.value,
|
fontFamily:
|
||||||
|
FontFamily.twEmoji.value,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -115,11 +115,10 @@ class ProxyCard extends StatelessWidget {
|
|||||||
groupName,
|
groupName,
|
||||||
nextProxyName,
|
nextProxyName,
|
||||||
);
|
);
|
||||||
appController.changeProxy(
|
await appController.changeProxyDebounce([
|
||||||
groupName: groupName,
|
groupName,
|
||||||
proxyName: nextProxyName,
|
nextProxyName,
|
||||||
);
|
]);
|
||||||
await appController.updateGroupDebounce();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
globalState.showSnackBar(
|
globalState.showSnackBar(
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import 'dart:io';
|
|||||||
import 'package:fl_clash/clash/clash.dart';
|
import 'package:fl_clash/clash/clash.dart';
|
||||||
import 'package:fl_clash/common/common.dart';
|
import 'package:fl_clash/common/common.dart';
|
||||||
import 'package:fl_clash/models/app.dart';
|
import 'package:fl_clash/models/app.dart';
|
||||||
import 'package:fl_clash/models/ffi.dart';
|
import 'package:fl_clash/models/core.dart';
|
||||||
import 'package:fl_clash/state.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';
|
||||||
@@ -56,7 +56,7 @@ class _ProvidersState extends State<Providers> {
|
|||||||
providerName: provider.name,
|
providerName: provider.name,
|
||||||
);
|
);
|
||||||
appState.setProvider(
|
appState.setProvider(
|
||||||
clashCore.getExternalProvider(provider.name),
|
await clashCore.getExternalProvider(provider.name),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -122,7 +122,7 @@ class ProviderItem extends StatelessWidget {
|
|||||||
if (message.isNotEmpty) throw message;
|
if (message.isNotEmpty) throw message;
|
||||||
});
|
});
|
||||||
appState.setProvider(
|
appState.setProvider(
|
||||||
clashCore.getExternalProvider(provider.name),
|
await clashCore.getExternalProvider(provider.name),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
await globalState.appController.updateGroupDebounce();
|
await globalState.appController.updateGroupDebounce();
|
||||||
@@ -143,7 +143,7 @@ class ProviderItem extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
if (message.isNotEmpty) throw message;
|
if (message.isNotEmpty) throw message;
|
||||||
appState.setProvider(
|
appState.setProvider(
|
||||||
clashCore.getExternalProvider(provider.name),
|
await clashCore.getExternalProvider(provider.name),
|
||||||
);
|
);
|
||||||
if (message.isNotEmpty) throw message;
|
if (message.isNotEmpty) throw message;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
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';
|
||||||
@@ -10,6 +11,8 @@ import 'package:provider/provider.dart';
|
|||||||
import 'card.dart';
|
import 'card.dart';
|
||||||
import 'common.dart';
|
import 'common.dart';
|
||||||
|
|
||||||
|
List<Proxy> currentProxies = [];
|
||||||
|
|
||||||
typedef GroupNameKeyMap = Map<String, GlobalObjectKey<ProxyGroupViewState>>;
|
typedef GroupNameKeyMap = Map<String, GlobalObjectKey<ProxyGroupViewState>>;
|
||||||
|
|
||||||
class ProxiesTabFragment extends StatefulWidget {
|
class ProxiesTabFragment extends StatefulWidget {
|
||||||
@@ -28,7 +31,7 @@ class ProxiesTabFragmentState extends State<ProxiesTabFragment>
|
|||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
_tabController?.dispose();
|
_destroyTabController();
|
||||||
}
|
}
|
||||||
|
|
||||||
scrollToGroupSelected() {
|
scrollToGroupSelected() {
|
||||||
@@ -106,6 +109,36 @@ class ProxiesTabFragmentState extends State<ProxiesTabFragment>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_tabControllerListener([int? index]) {
|
||||||
|
final appController = globalState.appController;
|
||||||
|
final currentGroups = appController.appState.currentGroups;
|
||||||
|
if (_tabController?.index == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final currentGroup = currentGroups[index ?? _tabController!.index];
|
||||||
|
currentProxies = currentGroup.all;
|
||||||
|
appController.config.updateCurrentGroupName(
|
||||||
|
currentGroup.name,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_destroyTabController() {
|
||||||
|
_tabController?.removeListener(_tabControllerListener);
|
||||||
|
_tabController?.dispose();
|
||||||
|
_tabController = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_updateTabController(int length, int index) {
|
||||||
|
final realIndex = index == -1 ? 0 : index;
|
||||||
|
_tabController ??= TabController(
|
||||||
|
length: length,
|
||||||
|
initialIndex: realIndex,
|
||||||
|
vsync: this,
|
||||||
|
);
|
||||||
|
_tabControllerListener(realIndex);
|
||||||
|
_tabController?.addListener(_tabControllerListener);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Selector2<AppState, Config, ProxiesSelectorState>(
|
return Selector2<AppState, Config, ProxiesSelectorState>(
|
||||||
@@ -119,8 +152,7 @@ class ProxiesTabFragmentState extends State<ProxiesTabFragment>
|
|||||||
},
|
},
|
||||||
shouldRebuild: (prev, next) {
|
shouldRebuild: (prev, next) {
|
||||||
if (!stringListEquality.equals(prev.groupNames, next.groupNames)) {
|
if (!stringListEquality.equals(prev.groupNames, next.groupNames)) {
|
||||||
_tabController?.dispose();
|
_destroyTabController();
|
||||||
_tabController = null;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -129,12 +161,8 @@ class ProxiesTabFragmentState extends State<ProxiesTabFragment>
|
|||||||
final index = state.groupNames.indexWhere(
|
final index = state.groupNames.indexWhere(
|
||||||
(item) => item == state.currentGroupName,
|
(item) => item == state.currentGroupName,
|
||||||
);
|
);
|
||||||
_tabController ??= TabController(
|
_updateTabController(state.groupNames.length, index);
|
||||||
length: state.groupNames.length,
|
final GroupNameKeyMap keyMap = {};
|
||||||
initialIndex: index == -1 ? 0 : index,
|
|
||||||
vsync: this,
|
|
||||||
);
|
|
||||||
GroupNameKeyMap keyMap = {};
|
|
||||||
final children = state.groupNames.map((groupName) {
|
final children = state.groupNames.map((groupName) {
|
||||||
keyMap[groupName] = GlobalObjectKey(groupName);
|
keyMap[groupName] = GlobalObjectKey(groupName);
|
||||||
return KeepScope(
|
return KeepScope(
|
||||||
@@ -167,16 +195,6 @@ class ProxiesTabFragmentState extends State<ProxiesTabFragment>
|
|||||||
left: 16,
|
left: 16,
|
||||||
right: 16 + (value ? 16 : 0),
|
right: 16 + (value ? 16 : 0),
|
||||||
),
|
),
|
||||||
onTap: (index) {
|
|
||||||
final appController = globalState.appController;
|
|
||||||
final currentGroups =
|
|
||||||
appController.appState.currentGroups;
|
|
||||||
if (currentGroups.length > index) {
|
|
||||||
appController.config.updateCurrentGroupName(
|
|
||||||
currentGroups[index].name,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
dividerColor: Colors.transparent,
|
dividerColor: Colors.transparent,
|
||||||
isScrollable: true,
|
isScrollable: true,
|
||||||
tabAlignment: TabAlignment.start,
|
tabAlignment: TabAlignment.start,
|
||||||
@@ -243,14 +261,13 @@ class ProxyGroupView extends StatefulWidget {
|
|||||||
class ProxyGroupViewState extends State<ProxyGroupView> {
|
class ProxyGroupViewState extends State<ProxyGroupView> {
|
||||||
var isLock = false;
|
var isLock = false;
|
||||||
final _controller = ScrollController();
|
final _controller = ScrollController();
|
||||||
List<Proxy> _lastProxies = [];
|
|
||||||
|
|
||||||
String get groupName => widget.groupName;
|
String get groupName => widget.groupName;
|
||||||
|
|
||||||
_delayTest(List<Proxy> proxies) async {
|
_delayTest() async {
|
||||||
if (isLock) return;
|
if (isLock) return;
|
||||||
isLock = true;
|
isLock = true;
|
||||||
await delayTest(proxies);
|
await delayTest(currentProxies);
|
||||||
isLock = false;
|
isLock = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -269,7 +286,7 @@ class ProxyGroupViewState extends State<ProxyGroupView> {
|
|||||||
16 +
|
16 +
|
||||||
getScrollToSelectedOffset(
|
getScrollToSelectedOffset(
|
||||||
groupName: groupName,
|
groupName: groupName,
|
||||||
proxies: _lastProxies,
|
proxies: currentProxies,
|
||||||
),
|
),
|
||||||
_controller.position.maxScrollExtent,
|
_controller.position.maxScrollExtent,
|
||||||
),
|
),
|
||||||
@@ -278,7 +295,7 @@ class ProxyGroupViewState extends State<ProxyGroupView> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
initFab(bool isCurrent, List<Proxy> proxies) {
|
initFab(bool isCurrent) {
|
||||||
if (!isCurrent) {
|
if (!isCurrent) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -287,9 +304,7 @@ class ProxyGroupViewState extends State<ProxyGroupView> {
|
|||||||
context.findAncestorStateOfType<CommonScaffoldState>();
|
context.findAncestorStateOfType<CommonScaffoldState>();
|
||||||
commonScaffoldState?.floatingActionButton = DelayTestButton(
|
commonScaffoldState?.floatingActionButton = DelayTestButton(
|
||||||
onClick: () async {
|
onClick: () async {
|
||||||
await _delayTest(
|
await _delayTest();
|
||||||
proxies,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -319,11 +334,10 @@ class ProxyGroupViewState extends State<ProxyGroupView> {
|
|||||||
final sortedProxies = globalState.appController.getSortProxies(
|
final sortedProxies = globalState.appController.getSortProxies(
|
||||||
proxies,
|
proxies,
|
||||||
);
|
);
|
||||||
_lastProxies = sortedProxies;
|
|
||||||
return ActiveBuilder(
|
return ActiveBuilder(
|
||||||
label: "proxies",
|
label: "proxies",
|
||||||
builder: (isCurrent, child) {
|
builder: (isCurrent, child) {
|
||||||
initFab(isCurrent, proxies);
|
initFab(isCurrent);
|
||||||
return child!;
|
return child!;
|
||||||
},
|
},
|
||||||
child: Align(
|
child: Align(
|
||||||
@@ -381,8 +395,10 @@ class _DelayTestButtonState extends State<DelayTestButton>
|
|||||||
_healthcheck() async {
|
_healthcheck() async {
|
||||||
_controller.forward();
|
_controller.forward();
|
||||||
await widget.onClick();
|
await widget.onClick();
|
||||||
|
if (mounted) {
|
||||||
_controller.reverse();
|
_controller.reverse();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
|||||||
@@ -330,5 +330,7 @@
|
|||||||
"routeMode_bypassPrivate": "Bypass private route address",
|
"routeMode_bypassPrivate": "Bypass private route address",
|
||||||
"routeMode_config": "Use config",
|
"routeMode_config": "Use config",
|
||||||
"routeAddress": "Route address",
|
"routeAddress": "Route address",
|
||||||
"routeAddressDesc": "Config listen route address"
|
"routeAddressDesc": "Config listen route address",
|
||||||
|
"pleaseInputAdminPassword": "Please enter the admin password",
|
||||||
|
"copyEnvVar": "Copying environment variables"
|
||||||
}
|
}
|
||||||
@@ -330,5 +330,7 @@
|
|||||||
"routeMode_bypassPrivate": "绕过私有路由地址",
|
"routeMode_bypassPrivate": "绕过私有路由地址",
|
||||||
"routeMode_config": "使用配置",
|
"routeMode_config": "使用配置",
|
||||||
"routeAddress": "路由地址",
|
"routeAddress": "路由地址",
|
||||||
"routeAddressDesc": "配置监听路由地址"
|
"routeAddressDesc": "配置监听路由地址",
|
||||||
|
"pleaseInputAdminPassword": "请输入管理员密码",
|
||||||
|
"copyEnvVar": "复制环境变量"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -121,6 +121,8 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||||||
"View current connections data"),
|
"View current connections data"),
|
||||||
"connectivity": MessageLookupByLibrary.simpleMessage("Connectivity:"),
|
"connectivity": MessageLookupByLibrary.simpleMessage("Connectivity:"),
|
||||||
"copy": MessageLookupByLibrary.simpleMessage("Copy"),
|
"copy": MessageLookupByLibrary.simpleMessage("Copy"),
|
||||||
|
"copyEnvVar": MessageLookupByLibrary.simpleMessage(
|
||||||
|
"Copying environment variables"),
|
||||||
"core": MessageLookupByLibrary.simpleMessage("Core"),
|
"core": MessageLookupByLibrary.simpleMessage("Core"),
|
||||||
"coreInfo": MessageLookupByLibrary.simpleMessage("Core info"),
|
"coreInfo": MessageLookupByLibrary.simpleMessage("Core info"),
|
||||||
"country": MessageLookupByLibrary.simpleMessage("Country"),
|
"country": MessageLookupByLibrary.simpleMessage("Country"),
|
||||||
@@ -326,6 +328,8 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||||||
"paste": MessageLookupByLibrary.simpleMessage("Paste"),
|
"paste": MessageLookupByLibrary.simpleMessage("Paste"),
|
||||||
"pleaseBindWebDAV":
|
"pleaseBindWebDAV":
|
||||||
MessageLookupByLibrary.simpleMessage("Please bind WebDAV"),
|
MessageLookupByLibrary.simpleMessage("Please bind WebDAV"),
|
||||||
|
"pleaseInputAdminPassword": MessageLookupByLibrary.simpleMessage(
|
||||||
|
"Please enter the admin password"),
|
||||||
"pleaseUploadFile":
|
"pleaseUploadFile":
|
||||||
MessageLookupByLibrary.simpleMessage("Please upload file"),
|
MessageLookupByLibrary.simpleMessage("Please upload file"),
|
||||||
"pleaseUploadValidQrcode": MessageLookupByLibrary.simpleMessage(
|
"pleaseUploadValidQrcode": MessageLookupByLibrary.simpleMessage(
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||||||
"connectionsDesc": MessageLookupByLibrary.simpleMessage("查看当前连接数据"),
|
"connectionsDesc": MessageLookupByLibrary.simpleMessage("查看当前连接数据"),
|
||||||
"connectivity": MessageLookupByLibrary.simpleMessage("连通性:"),
|
"connectivity": MessageLookupByLibrary.simpleMessage("连通性:"),
|
||||||
"copy": MessageLookupByLibrary.simpleMessage("复制"),
|
"copy": MessageLookupByLibrary.simpleMessage("复制"),
|
||||||
|
"copyEnvVar": MessageLookupByLibrary.simpleMessage("复制环境变量"),
|
||||||
"core": MessageLookupByLibrary.simpleMessage("内核"),
|
"core": MessageLookupByLibrary.simpleMessage("内核"),
|
||||||
"coreInfo": MessageLookupByLibrary.simpleMessage("内核信息"),
|
"coreInfo": MessageLookupByLibrary.simpleMessage("内核信息"),
|
||||||
"country": MessageLookupByLibrary.simpleMessage("区域"),
|
"country": MessageLookupByLibrary.simpleMessage("区域"),
|
||||||
@@ -259,6 +260,8 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||||||
"passwordTip": MessageLookupByLibrary.simpleMessage("密码不能为空"),
|
"passwordTip": MessageLookupByLibrary.simpleMessage("密码不能为空"),
|
||||||
"paste": MessageLookupByLibrary.simpleMessage("粘贴"),
|
"paste": MessageLookupByLibrary.simpleMessage("粘贴"),
|
||||||
"pleaseBindWebDAV": MessageLookupByLibrary.simpleMessage("请绑定WebDAV"),
|
"pleaseBindWebDAV": MessageLookupByLibrary.simpleMessage("请绑定WebDAV"),
|
||||||
|
"pleaseInputAdminPassword":
|
||||||
|
MessageLookupByLibrary.simpleMessage("请输入管理员密码"),
|
||||||
"pleaseUploadFile": MessageLookupByLibrary.simpleMessage("请上传文件"),
|
"pleaseUploadFile": MessageLookupByLibrary.simpleMessage("请上传文件"),
|
||||||
"pleaseUploadValidQrcode":
|
"pleaseUploadValidQrcode":
|
||||||
MessageLookupByLibrary.simpleMessage("请上传有效的二维码"),
|
MessageLookupByLibrary.simpleMessage("请上传有效的二维码"),
|
||||||
|
|||||||
@@ -3369,6 +3369,26 @@ class AppLocalizations {
|
|||||||
args: [],
|
args: [],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `Please enter the admin password`
|
||||||
|
String get pleaseInputAdminPassword {
|
||||||
|
return Intl.message(
|
||||||
|
'Please enter the admin password',
|
||||||
|
name: 'pleaseInputAdminPassword',
|
||||||
|
desc: '',
|
||||||
|
args: [],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `Copying environment variables`
|
||||||
|
String get copyEnvVar {
|
||||||
|
return Intl.message(
|
||||||
|
'Copying environment variables',
|
||||||
|
name: 'copyEnvVar',
|
||||||
|
desc: '',
|
||||||
|
args: [],
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class AppLocalizationDelegate extends LocalizationsDelegate<AppLocalizations> {
|
class AppLocalizationDelegate extends LocalizationsDelegate<AppLocalizations> {
|
||||||
|
|||||||
@@ -8,14 +8,15 @@ import 'package:fl_clash/plugins/vpn.dart';
|
|||||||
import 'package:fl_clash/state.dart';
|
import 'package:fl_clash/state.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:package_info_plus/package_info_plus.dart';
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
|
|
||||||
import 'application.dart';
|
import 'application.dart';
|
||||||
|
import 'common/common.dart';
|
||||||
import 'l10n/l10n.dart';
|
import 'l10n/l10n.dart';
|
||||||
import 'models/models.dart';
|
import 'models/models.dart';
|
||||||
import 'common/common.dart';
|
|
||||||
|
|
||||||
Future<void> main() async {
|
Future<void> main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
clashCore.initMessage();
|
clashLib?.initMessage();
|
||||||
globalState.packageInfo = await PackageInfo.fromPlatform();
|
globalState.packageInfo = await PackageInfo.fromPlatform();
|
||||||
final version = await system.version;
|
final version = await system.version;
|
||||||
final config = await preferences.getConfig() ?? Config();
|
final config = await preferences.getConfig() ?? Config();
|
||||||
@@ -36,17 +37,12 @@ Future<void> main() async {
|
|||||||
openLogs: config.appSetting.openLogs,
|
openLogs: config.appSetting.openLogs,
|
||||||
hasProxies: false,
|
hasProxies: false,
|
||||||
);
|
);
|
||||||
globalState.updateTray(
|
tray.update(
|
||||||
appState: appState,
|
appState: appState,
|
||||||
appFlowingState: appFlowingState,
|
appFlowingState: appFlowingState,
|
||||||
config: config,
|
config: config,
|
||||||
clashConfig: clashConfig,
|
clashConfig: clashConfig,
|
||||||
);
|
);
|
||||||
await globalState.init(
|
|
||||||
appState: appState,
|
|
||||||
config: config,
|
|
||||||
clashConfig: clashConfig,
|
|
||||||
);
|
|
||||||
HttpOverrides.global = FlClashHttpOverrides();
|
HttpOverrides.global = FlClashHttpOverrides();
|
||||||
runAppWithPreferences(
|
runAppWithPreferences(
|
||||||
const Application(),
|
const Application(),
|
||||||
@@ -69,39 +65,66 @@ Future<void> vpnService() async {
|
|||||||
other.getLocaleForString(config.appSetting.locale) ??
|
other.getLocaleForString(config.appSetting.locale) ??
|
||||||
WidgetsBinding.instance.platformDispatcher.locale,
|
WidgetsBinding.instance.platformDispatcher.locale,
|
||||||
);
|
);
|
||||||
|
|
||||||
final appState = AppState(
|
final appState = AppState(
|
||||||
mode: clashConfig.mode,
|
mode: clashConfig.mode,
|
||||||
selectedMap: config.currentSelectedMap,
|
selectedMap: config.currentSelectedMap,
|
||||||
version: version,
|
version: version,
|
||||||
);
|
);
|
||||||
|
|
||||||
await globalState.init(
|
await globalState.init(
|
||||||
appState: appState,
|
appState: appState,
|
||||||
config: config,
|
config: config,
|
||||||
clashConfig: clashConfig,
|
clashConfig: clashConfig,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await app?.tip(appLocalizations.startVpn);
|
||||||
|
|
||||||
|
globalState
|
||||||
|
.updateClashConfig(
|
||||||
|
appState: appState,
|
||||||
|
clashConfig: clashConfig,
|
||||||
|
config: config,
|
||||||
|
isPatch: false,
|
||||||
|
)
|
||||||
|
.then(
|
||||||
|
(_) async {
|
||||||
|
await globalState.handleStart();
|
||||||
|
|
||||||
|
tile?.addListener(
|
||||||
|
TileListenerWithVpn(
|
||||||
|
onStop: () async {
|
||||||
|
await app?.tip(appLocalizations.stopVpn);
|
||||||
|
await globalState.handleStop();
|
||||||
|
clashCore.shutdown();
|
||||||
|
exit(0);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
globalState.updateTraffic(config: config);
|
||||||
|
globalState.updateFunctionLists = [
|
||||||
|
() {
|
||||||
|
globalState.updateTraffic(config: config);
|
||||||
|
}
|
||||||
|
];
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
vpn?.setServiceMessageHandler(
|
vpn?.setServiceMessageHandler(
|
||||||
ServiceMessageHandler(
|
ServiceMessageHandler(
|
||||||
onProtect: (Fd fd) async {
|
onProtect: (Fd fd) async {
|
||||||
await vpn?.setProtect(fd.value);
|
await vpn?.setProtect(fd.value);
|
||||||
clashCore.setFdMap(fd.id);
|
clashLib?.setFdMap(fd.id);
|
||||||
},
|
},
|
||||||
onProcess: (Process process) async {
|
onProcess: (ProcessData process) async {
|
||||||
final packageName = await vpn?.resolverProcess(process);
|
final packageName = await vpn?.resolverProcess(process);
|
||||||
clashCore.setProcessMap(
|
clashLib?.setProcessMap(
|
||||||
ProcessMapItem(
|
ProcessMapItem(
|
||||||
id: process.id,
|
id: process.id,
|
||||||
value: packageName ?? "",
|
value: packageName ?? "",
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
onStarted: (String runTime) async {
|
|
||||||
await globalState.applyProfile(
|
|
||||||
appState: appState,
|
|
||||||
config: config,
|
|
||||||
clashConfig: clashConfig,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
onLoaded: (String groupName) {
|
onLoaded: (String groupName) {
|
||||||
final currentSelectedMap = config.currentSelectedMap;
|
final currentSelectedMap = config.currentSelectedMap;
|
||||||
final proxyName = currentSelectedMap[groupName];
|
final proxyName = currentSelectedMap[groupName];
|
||||||
@@ -114,43 +137,20 @@ Future<void> vpnService() async {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
await app?.tip(appLocalizations.startVpn);
|
|
||||||
await globalState.handleStart();
|
|
||||||
|
|
||||||
tile?.addListener(
|
|
||||||
TileListenerWithVpn(
|
|
||||||
onStop: () async {
|
|
||||||
await app?.tip(appLocalizations.stopVpn);
|
|
||||||
await globalState.handleStop();
|
|
||||||
clashCore.shutdown();
|
|
||||||
exit(0);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
globalState.updateTraffic();
|
|
||||||
globalState.updateFunctionLists = [
|
|
||||||
() {
|
|
||||||
globalState.updateTraffic();
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@immutable
|
@immutable
|
||||||
class ServiceMessageHandler with ServiceMessageListener {
|
class ServiceMessageHandler with ServiceMessageListener {
|
||||||
final Function(Fd fd) _onProtect;
|
final Function(Fd fd) _onProtect;
|
||||||
final Function(Process process) _onProcess;
|
final Function(ProcessData process) _onProcess;
|
||||||
final Function(String runTime) _onStarted;
|
|
||||||
final Function(String providerName) _onLoaded;
|
final Function(String providerName) _onLoaded;
|
||||||
|
|
||||||
const ServiceMessageHandler({
|
const ServiceMessageHandler({
|
||||||
required Function(Fd fd) onProtect,
|
required Function(Fd fd) onProtect,
|
||||||
required Function(Process process) onProcess,
|
required Function(ProcessData process) onProcess,
|
||||||
required Function(String runTime) onStarted,
|
|
||||||
required Function(String providerName) onLoaded,
|
required Function(String providerName) onLoaded,
|
||||||
}) : _onProtect = onProtect,
|
}) : _onProtect = onProtect,
|
||||||
_onProcess = onProcess,
|
_onProcess = onProcess,
|
||||||
_onStarted = onStarted,
|
|
||||||
_onLoaded = onLoaded;
|
_onLoaded = onLoaded;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -159,15 +159,10 @@ class ServiceMessageHandler with ServiceMessageListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
onProcess(Process process) {
|
onProcess(ProcessData process) {
|
||||||
_onProcess(process);
|
_onProcess(process);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
onStarted(String runTime) {
|
|
||||||
_onStarted(runTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
onLoaded(String providerName) {
|
onLoaded(String providerName) {
|
||||||
_onLoaded(providerName);
|
_onLoaded(providerName);
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:fl_clash/clash/clash.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/plugins/app.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@@ -23,6 +24,28 @@ class _AndroidContainerState extends State<AndroidManager> {
|
|||||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
|
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _updateCoreState(Widget child) {
|
||||||
|
return Selector2<Config, ClashConfig, CoreState>(
|
||||||
|
selector: (_, config, clashConfig) => CoreState(
|
||||||
|
enable: config.vpnProps.enable,
|
||||||
|
accessControl: config.isAccessControl ? config.accessControl : null,
|
||||||
|
ipv6: config.vpnProps.ipv6,
|
||||||
|
allowBypass: config.vpnProps.allowBypass,
|
||||||
|
bypassDomain: config.networkProps.bypassDomain,
|
||||||
|
systemProxy: config.vpnProps.systemProxy,
|
||||||
|
onlyProxy: config.appSetting.onlyProxy,
|
||||||
|
currentProfileName:
|
||||||
|
config.currentProfile?.label ?? config.currentProfileId ?? "",
|
||||||
|
routeAddress: clashConfig.routeAddress,
|
||||||
|
),
|
||||||
|
builder: (__, state, child) {
|
||||||
|
clashLib?.setState(state);
|
||||||
|
return child!;
|
||||||
|
},
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget _excludeContainer(Widget child) {
|
Widget _excludeContainer(Widget child) {
|
||||||
return Selector<Config, bool>(
|
return Selector<Config, bool>(
|
||||||
selector: (_, config) => config.appSetting.hidden,
|
selector: (_, config) => config.appSetting.hidden,
|
||||||
@@ -36,6 +59,10 @@ class _AndroidContainerState extends State<AndroidManager> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return _excludeContainer(widget.child);
|
return _updateCoreState(
|
||||||
|
_excludeContainer(
|
||||||
|
widget.child,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,41 +57,20 @@ class _ClashContainerState extends State<ClashManager> with AppMessageListener {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _updateCoreState(Widget child) {
|
|
||||||
return Selector2<Config, ClashConfig, CoreState>(
|
|
||||||
selector: (_, config, clashConfig) => CoreState(
|
|
||||||
enable: config.vpnProps.enable,
|
|
||||||
accessControl: config.isAccessControl ? config.accessControl : null,
|
|
||||||
ipv6: config.vpnProps.ipv6,
|
|
||||||
allowBypass: config.vpnProps.allowBypass,
|
|
||||||
bypassDomain: config.networkProps.bypassDomain,
|
|
||||||
systemProxy: config.vpnProps.systemProxy,
|
|
||||||
onlyProxy: config.appSetting.onlyProxy,
|
|
||||||
currentProfileName:
|
|
||||||
config.currentProfile?.label ?? config.currentProfileId ?? "",
|
|
||||||
routeAddress: clashConfig.routeAddress,
|
|
||||||
),
|
|
||||||
builder: (__, state, child) {
|
|
||||||
clashCore.setState(state);
|
|
||||||
return child!;
|
|
||||||
},
|
|
||||||
child: child,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
_changeProfile() async {
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
|
||||||
final appController = globalState.appController;
|
|
||||||
appController.appState.delayMap = {};
|
|
||||||
await appController.applyProfile();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _changeProfileContainer(Widget child) {
|
Widget _changeProfileContainer(Widget child) {
|
||||||
return Selector<Config, String?>(
|
return Selector<Config, String?>(
|
||||||
selector: (_, config) => config.currentProfileId,
|
selector: (_, config) => config.currentProfileId,
|
||||||
|
shouldRebuild: (prev, next) {
|
||||||
|
if (prev != next) {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
final appController = globalState.appController;
|
||||||
|
appController.appState.delayMap = {};
|
||||||
|
appController.applyProfile();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return prev != next;
|
||||||
|
},
|
||||||
builder: (__, state, child) {
|
builder: (__, state, child) {
|
||||||
_changeProfile();
|
|
||||||
return child!;
|
return child!;
|
||||||
},
|
},
|
||||||
child: child,
|
child: child,
|
||||||
@@ -101,11 +80,9 @@ class _ClashContainerState extends State<ClashManager> with AppMessageListener {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return _changeProfileContainer(
|
return _changeProfileContainer(
|
||||||
_updateCoreState(
|
|
||||||
_updateContainer(
|
_updateContainer(
|
||||||
widget.child,
|
widget.child,
|
||||||
),
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,7 +135,7 @@ class _ClashContainerState extends State<ClashManager> with AppMessageListener {
|
|||||||
Future<void> onLoaded(String providerName) async {
|
Future<void> onLoaded(String providerName) async {
|
||||||
final appController = globalState.appController;
|
final appController = globalState.appController;
|
||||||
appController.appState.setProvider(
|
appController.appState.setProvider(
|
||||||
clashCore.getExternalProvider(
|
await clashCore.getExternalProvider(
|
||||||
providerName,
|
providerName,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -30,13 +30,15 @@ class _TrayContainerState extends State<TrayManager> with TrayListener {
|
|||||||
selector: (_, appState, appFlowingState, config, clashConfig) =>
|
selector: (_, appState, appFlowingState, config, clashConfig) =>
|
||||||
TrayState(
|
TrayState(
|
||||||
mode: clashConfig.mode,
|
mode: clashConfig.mode,
|
||||||
adminAutoLaunch: config.appSetting.adminAutoLaunch,
|
|
||||||
autoLaunch: config.appSetting.autoLaunch,
|
autoLaunch: config.appSetting.autoLaunch,
|
||||||
isStart: appFlowingState.isStart,
|
isStart: appFlowingState.isStart,
|
||||||
locale: config.appSetting.locale,
|
locale: config.appSetting.locale,
|
||||||
systemProxy: config.networkProps.systemProxy,
|
systemProxy: config.networkProps.systemProxy,
|
||||||
tunEnable: clashConfig.tun.enable,
|
tunEnable: clashConfig.tun.enable,
|
||||||
brightness: appState.brightness,
|
brightness: appState.brightness,
|
||||||
|
port: clashConfig.mixedPort,
|
||||||
|
groups: appState.groups,
|
||||||
|
map: appState.selectedMap,
|
||||||
),
|
),
|
||||||
shouldRebuild: (prev, next) {
|
shouldRebuild: (prev, next) {
|
||||||
if (prev != next) {
|
if (prev != next) {
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ class _WindowContainerState extends State<WindowManager>
|
|||||||
return Selector<Config, AutoLaunchState>(
|
return Selector<Config, AutoLaunchState>(
|
||||||
selector: (_, config) => AutoLaunchState(
|
selector: (_, config) => AutoLaunchState(
|
||||||
isAutoLaunch: config.appSetting.autoLaunch,
|
isAutoLaunch: config.appSetting.autoLaunch,
|
||||||
isAdminAutoLaunch: config.appSetting.adminAutoLaunch,
|
|
||||||
),
|
),
|
||||||
builder: (_, state, child) {
|
builder: (_, state, child) {
|
||||||
updateLaunchDebounce ??= debounce((AutoLaunchState state) {
|
updateLaunchDebounce ??= debounce((AutoLaunchState state) {
|
||||||
@@ -88,8 +87,9 @@ class _WindowContainerState extends State<WindowManager>
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onTaskbarCreated() {
|
Future<void> onTaskbarCreated() async {
|
||||||
globalState.appController.updateTray(true);
|
globalState.appController.updateTray(true);
|
||||||
|
await globalState.appController.restartCore();
|
||||||
super.onTaskbarCreated();
|
super.onTaskbarCreated();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
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:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'common.dart';
|
import 'common.dart';
|
||||||
import 'ffi.dart';
|
import 'core.dart';
|
||||||
import 'profile.dart';
|
import 'profile.dart';
|
||||||
|
|
||||||
typedef DelayMap = Map<String, int?>;
|
typedef DelayMap = Map<String, int?>;
|
||||||
|
|||||||
@@ -482,6 +482,28 @@ class ClashConfig extends ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ClashConfig copyWith() {
|
||||||
|
return ClashConfig()
|
||||||
|
..mixedPort = _mixedPort
|
||||||
|
..mode = _mode
|
||||||
|
..ipv6 = _ipv6
|
||||||
|
..findProcessMode = _findProcessMode
|
||||||
|
..allowLan = _allowLan
|
||||||
|
..tcpConcurrent = _tcpConcurrent
|
||||||
|
..logLevel = _logLevel
|
||||||
|
..tun = tun
|
||||||
|
..unifiedDelay = _unifiedDelay
|
||||||
|
..geodataLoader = _geodataLoader
|
||||||
|
..externalController = _externalController
|
||||||
|
..keepAliveInterval = _keepAliveInterval
|
||||||
|
..dns = _dns
|
||||||
|
..geoXUrl = _geoXUrl
|
||||||
|
..routeMode = _routeMode
|
||||||
|
..includeRouteAddress = _includeRouteAddress
|
||||||
|
..rules = _rules
|
||||||
|
..hosts = _hosts;
|
||||||
|
}
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
return _$ClashConfigToJson(this);
|
return _$ClashConfigToJson(this);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ class AppSetting with _$AppSetting {
|
|||||||
String? locale,
|
String? locale,
|
||||||
@Default(false) bool onlyProxy,
|
@Default(false) bool onlyProxy,
|
||||||
@Default(false) bool autoLaunch,
|
@Default(false) bool autoLaunch,
|
||||||
@Default(false) bool adminAutoLaunch,
|
|
||||||
@Default(false) bool silentLaunch,
|
@Default(false) bool silentLaunch,
|
||||||
@Default(false) bool autoRun,
|
@Default(false) bool autoRun,
|
||||||
@Default(false) bool openLogs,
|
@Default(false) bool openLogs,
|
||||||
|
|||||||
@@ -1,11 +1,35 @@
|
|||||||
// ignore_for_file: invalid_annotation_target
|
// ignore_for_file: invalid_annotation_target
|
||||||
|
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
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';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
part 'generated/ffi.freezed.dart';
|
part 'generated/core.freezed.dart';
|
||||||
part 'generated/ffi.g.dart';
|
part 'generated/core.g.dart';
|
||||||
|
|
||||||
|
abstract mixin class AppMessageListener {
|
||||||
|
void onLog(Log log) {}
|
||||||
|
|
||||||
|
void onDelay(Delay delay) {}
|
||||||
|
|
||||||
|
void onRequest(Connection connection) {}
|
||||||
|
|
||||||
|
void onStarted(String runTime) {}
|
||||||
|
|
||||||
|
void onLoaded(String providerName) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract mixin class ServiceMessageListener {
|
||||||
|
onProtect(Fd fd) {}
|
||||||
|
|
||||||
|
onProcess(ProcessData process) {}
|
||||||
|
|
||||||
|
onStarted(String runTime) {}
|
||||||
|
|
||||||
|
onLoaded(String providerName) {}
|
||||||
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
class CoreState with _$CoreState {
|
class CoreState with _$CoreState {
|
||||||
@@ -124,14 +148,14 @@ class Now with _$Now {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
class Process with _$Process {
|
class ProcessData with _$ProcessData {
|
||||||
const factory Process({
|
const factory ProcessData({
|
||||||
required int id,
|
required int id,
|
||||||
required Metadata metadata,
|
required Metadata metadata,
|
||||||
}) = _Process;
|
}) = _ProcessData;
|
||||||
|
|
||||||
factory Process.fromJson(Map<String, Object?> json) =>
|
factory ProcessData.fromJson(Map<String, Object?> json) =>
|
||||||
_$ProcessFromJson(json);
|
_$ProcessDataFromJson(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
@@ -212,24 +236,19 @@ class TunProps with _$TunProps {
|
|||||||
_$TunPropsFromJson(json);
|
_$TunPropsFromJson(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract mixin class AppMessageListener {
|
@freezed
|
||||||
void onLog(Log log) {}
|
class Action with _$Action {
|
||||||
|
const factory Action({
|
||||||
|
required ActionMethod method,
|
||||||
|
required dynamic data,
|
||||||
|
required String id,
|
||||||
|
}) = _Action;
|
||||||
|
|
||||||
void onDelay(Delay delay) {}
|
factory Action.fromJson(Map<String, Object?> json) => _$ActionFromJson(json);
|
||||||
|
|
||||||
void onRequest(Connection connection) {}
|
|
||||||
|
|
||||||
void onStarted(String runTime) {}
|
|
||||||
|
|
||||||
void onLoaded(String providerName) {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract mixin class ServiceMessageListener {
|
extension ActionExt on Action {
|
||||||
onProtect(Fd fd) {}
|
String get toJson {
|
||||||
|
return json.encode(this);
|
||||||
onProcess(Process process) {}
|
}
|
||||||
|
|
||||||
onStarted(String runTime) {}
|
|
||||||
|
|
||||||
onLoaded(String providerName) {}
|
|
||||||
}
|
}
|
||||||
@@ -23,7 +23,6 @@ mixin _$AppSetting {
|
|||||||
String? get locale => throw _privateConstructorUsedError;
|
String? get locale => throw _privateConstructorUsedError;
|
||||||
bool get onlyProxy => throw _privateConstructorUsedError;
|
bool get onlyProxy => throw _privateConstructorUsedError;
|
||||||
bool get autoLaunch => throw _privateConstructorUsedError;
|
bool get autoLaunch => throw _privateConstructorUsedError;
|
||||||
bool get adminAutoLaunch => throw _privateConstructorUsedError;
|
|
||||||
bool get silentLaunch => throw _privateConstructorUsedError;
|
bool get silentLaunch => throw _privateConstructorUsedError;
|
||||||
bool get autoRun => throw _privateConstructorUsedError;
|
bool get autoRun => throw _privateConstructorUsedError;
|
||||||
bool get openLogs => throw _privateConstructorUsedError;
|
bool get openLogs => throw _privateConstructorUsedError;
|
||||||
@@ -56,7 +55,6 @@ abstract class $AppSettingCopyWith<$Res> {
|
|||||||
{String? locale,
|
{String? locale,
|
||||||
bool onlyProxy,
|
bool onlyProxy,
|
||||||
bool autoLaunch,
|
bool autoLaunch,
|
||||||
bool adminAutoLaunch,
|
|
||||||
bool silentLaunch,
|
bool silentLaunch,
|
||||||
bool autoRun,
|
bool autoRun,
|
||||||
bool openLogs,
|
bool openLogs,
|
||||||
@@ -88,7 +86,6 @@ class _$AppSettingCopyWithImpl<$Res, $Val extends AppSetting>
|
|||||||
Object? locale = freezed,
|
Object? locale = freezed,
|
||||||
Object? onlyProxy = null,
|
Object? onlyProxy = null,
|
||||||
Object? autoLaunch = null,
|
Object? autoLaunch = null,
|
||||||
Object? adminAutoLaunch = null,
|
|
||||||
Object? silentLaunch = null,
|
Object? silentLaunch = null,
|
||||||
Object? autoRun = null,
|
Object? autoRun = null,
|
||||||
Object? openLogs = null,
|
Object? openLogs = null,
|
||||||
@@ -114,10 +111,6 @@ class _$AppSettingCopyWithImpl<$Res, $Val extends AppSetting>
|
|||||||
? _value.autoLaunch
|
? _value.autoLaunch
|
||||||
: autoLaunch // ignore: cast_nullable_to_non_nullable
|
: autoLaunch // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,
|
as bool,
|
||||||
adminAutoLaunch: null == adminAutoLaunch
|
|
||||||
? _value.adminAutoLaunch
|
|
||||||
: adminAutoLaunch // ignore: cast_nullable_to_non_nullable
|
|
||||||
as bool,
|
|
||||||
silentLaunch: null == silentLaunch
|
silentLaunch: null == silentLaunch
|
||||||
? _value.silentLaunch
|
? _value.silentLaunch
|
||||||
: silentLaunch // ignore: cast_nullable_to_non_nullable
|
: silentLaunch // ignore: cast_nullable_to_non_nullable
|
||||||
@@ -178,7 +171,6 @@ abstract class _$$AppSettingImplCopyWith<$Res>
|
|||||||
{String? locale,
|
{String? locale,
|
||||||
bool onlyProxy,
|
bool onlyProxy,
|
||||||
bool autoLaunch,
|
bool autoLaunch,
|
||||||
bool adminAutoLaunch,
|
|
||||||
bool silentLaunch,
|
bool silentLaunch,
|
||||||
bool autoRun,
|
bool autoRun,
|
||||||
bool openLogs,
|
bool openLogs,
|
||||||
@@ -208,7 +200,6 @@ class __$$AppSettingImplCopyWithImpl<$Res>
|
|||||||
Object? locale = freezed,
|
Object? locale = freezed,
|
||||||
Object? onlyProxy = null,
|
Object? onlyProxy = null,
|
||||||
Object? autoLaunch = null,
|
Object? autoLaunch = null,
|
||||||
Object? adminAutoLaunch = null,
|
|
||||||
Object? silentLaunch = null,
|
Object? silentLaunch = null,
|
||||||
Object? autoRun = null,
|
Object? autoRun = null,
|
||||||
Object? openLogs = null,
|
Object? openLogs = null,
|
||||||
@@ -234,10 +225,6 @@ class __$$AppSettingImplCopyWithImpl<$Res>
|
|||||||
? _value.autoLaunch
|
? _value.autoLaunch
|
||||||
: autoLaunch // ignore: cast_nullable_to_non_nullable
|
: autoLaunch // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,
|
as bool,
|
||||||
adminAutoLaunch: null == adminAutoLaunch
|
|
||||||
? _value.adminAutoLaunch
|
|
||||||
: adminAutoLaunch // ignore: cast_nullable_to_non_nullable
|
|
||||||
as bool,
|
|
||||||
silentLaunch: null == silentLaunch
|
silentLaunch: null == silentLaunch
|
||||||
? _value.silentLaunch
|
? _value.silentLaunch
|
||||||
: silentLaunch // ignore: cast_nullable_to_non_nullable
|
: silentLaunch // ignore: cast_nullable_to_non_nullable
|
||||||
@@ -293,7 +280,6 @@ class _$AppSettingImpl implements _AppSetting {
|
|||||||
{this.locale,
|
{this.locale,
|
||||||
this.onlyProxy = false,
|
this.onlyProxy = false,
|
||||||
this.autoLaunch = false,
|
this.autoLaunch = false,
|
||||||
this.adminAutoLaunch = false,
|
|
||||||
this.silentLaunch = false,
|
this.silentLaunch = false,
|
||||||
this.autoRun = false,
|
this.autoRun = false,
|
||||||
this.openLogs = false,
|
this.openLogs = false,
|
||||||
@@ -319,9 +305,6 @@ class _$AppSettingImpl implements _AppSetting {
|
|||||||
final bool autoLaunch;
|
final bool autoLaunch;
|
||||||
@override
|
@override
|
||||||
@JsonKey()
|
@JsonKey()
|
||||||
final bool adminAutoLaunch;
|
|
||||||
@override
|
|
||||||
@JsonKey()
|
|
||||||
final bool silentLaunch;
|
final bool silentLaunch;
|
||||||
@override
|
@override
|
||||||
@JsonKey()
|
@JsonKey()
|
||||||
@@ -356,7 +339,7 @@ class _$AppSettingImpl implements _AppSetting {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'AppSetting(locale: $locale, onlyProxy: $onlyProxy, autoLaunch: $autoLaunch, adminAutoLaunch: $adminAutoLaunch, silentLaunch: $silentLaunch, autoRun: $autoRun, openLogs: $openLogs, closeConnections: $closeConnections, testUrl: $testUrl, isAnimateToPage: $isAnimateToPage, autoCheckUpdate: $autoCheckUpdate, showLabel: $showLabel, disclaimerAccepted: $disclaimerAccepted, minimizeOnExit: $minimizeOnExit, hidden: $hidden)';
|
return 'AppSetting(locale: $locale, onlyProxy: $onlyProxy, autoLaunch: $autoLaunch, silentLaunch: $silentLaunch, autoRun: $autoRun, openLogs: $openLogs, closeConnections: $closeConnections, testUrl: $testUrl, isAnimateToPage: $isAnimateToPage, autoCheckUpdate: $autoCheckUpdate, showLabel: $showLabel, disclaimerAccepted: $disclaimerAccepted, minimizeOnExit: $minimizeOnExit, hidden: $hidden)';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -369,8 +352,6 @@ class _$AppSettingImpl implements _AppSetting {
|
|||||||
other.onlyProxy == onlyProxy) &&
|
other.onlyProxy == onlyProxy) &&
|
||||||
(identical(other.autoLaunch, autoLaunch) ||
|
(identical(other.autoLaunch, autoLaunch) ||
|
||||||
other.autoLaunch == autoLaunch) &&
|
other.autoLaunch == autoLaunch) &&
|
||||||
(identical(other.adminAutoLaunch, adminAutoLaunch) ||
|
|
||||||
other.adminAutoLaunch == adminAutoLaunch) &&
|
|
||||||
(identical(other.silentLaunch, silentLaunch) ||
|
(identical(other.silentLaunch, silentLaunch) ||
|
||||||
other.silentLaunch == silentLaunch) &&
|
other.silentLaunch == silentLaunch) &&
|
||||||
(identical(other.autoRun, autoRun) || other.autoRun == autoRun) &&
|
(identical(other.autoRun, autoRun) || other.autoRun == autoRun) &&
|
||||||
@@ -399,7 +380,6 @@ class _$AppSettingImpl implements _AppSetting {
|
|||||||
locale,
|
locale,
|
||||||
onlyProxy,
|
onlyProxy,
|
||||||
autoLaunch,
|
autoLaunch,
|
||||||
adminAutoLaunch,
|
|
||||||
silentLaunch,
|
silentLaunch,
|
||||||
autoRun,
|
autoRun,
|
||||||
openLogs,
|
openLogs,
|
||||||
@@ -433,7 +413,6 @@ abstract class _AppSetting implements AppSetting {
|
|||||||
{final String? locale,
|
{final String? locale,
|
||||||
final bool onlyProxy,
|
final bool onlyProxy,
|
||||||
final bool autoLaunch,
|
final bool autoLaunch,
|
||||||
final bool adminAutoLaunch,
|
|
||||||
final bool silentLaunch,
|
final bool silentLaunch,
|
||||||
final bool autoRun,
|
final bool autoRun,
|
||||||
final bool openLogs,
|
final bool openLogs,
|
||||||
@@ -456,8 +435,6 @@ abstract class _AppSetting implements AppSetting {
|
|||||||
@override
|
@override
|
||||||
bool get autoLaunch;
|
bool get autoLaunch;
|
||||||
@override
|
@override
|
||||||
bool get adminAutoLaunch;
|
|
||||||
@override
|
|
||||||
bool get silentLaunch;
|
bool get silentLaunch;
|
||||||
@override
|
@override
|
||||||
bool get autoRun;
|
bool get autoRun;
|
||||||
|
|||||||
@@ -56,7 +56,6 @@ _$AppSettingImpl _$$AppSettingImplFromJson(Map<String, dynamic> json) =>
|
|||||||
locale: json['locale'] as String?,
|
locale: json['locale'] as String?,
|
||||||
onlyProxy: json['onlyProxy'] as bool? ?? false,
|
onlyProxy: json['onlyProxy'] as bool? ?? false,
|
||||||
autoLaunch: json['autoLaunch'] as bool? ?? false,
|
autoLaunch: json['autoLaunch'] as bool? ?? false,
|
||||||
adminAutoLaunch: json['adminAutoLaunch'] as bool? ?? false,
|
|
||||||
silentLaunch: json['silentLaunch'] as bool? ?? false,
|
silentLaunch: json['silentLaunch'] as bool? ?? false,
|
||||||
autoRun: json['autoRun'] as bool? ?? false,
|
autoRun: json['autoRun'] as bool? ?? false,
|
||||||
openLogs: json['openLogs'] as bool? ?? false,
|
openLogs: json['openLogs'] as bool? ?? false,
|
||||||
@@ -75,7 +74,6 @@ Map<String, dynamic> _$$AppSettingImplToJson(_$AppSettingImpl instance) =>
|
|||||||
'locale': instance.locale,
|
'locale': instance.locale,
|
||||||
'onlyProxy': instance.onlyProxy,
|
'onlyProxy': instance.onlyProxy,
|
||||||
'autoLaunch': instance.autoLaunch,
|
'autoLaunch': instance.autoLaunch,
|
||||||
'adminAutoLaunch': instance.adminAutoLaunch,
|
|
||||||
'silentLaunch': instance.silentLaunch,
|
'silentLaunch': instance.silentLaunch,
|
||||||
'autoRun': instance.autoRun,
|
'autoRun': instance.autoRun,
|
||||||
'openLogs': instance.openLogs,
|
'openLogs': instance.openLogs,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// ignore_for_file: type=lint
|
// ignore_for_file: type=lint
|
||||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||||
|
|
||||||
part of '../ffi.dart';
|
part of '../core.dart';
|
||||||
|
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
// FreezedGenerator
|
// FreezedGenerator
|
||||||
@@ -2080,28 +2080,30 @@ abstract class _Now implements Now {
|
|||||||
throw _privateConstructorUsedError;
|
throw _privateConstructorUsedError;
|
||||||
}
|
}
|
||||||
|
|
||||||
Process _$ProcessFromJson(Map<String, dynamic> json) {
|
ProcessData _$ProcessDataFromJson(Map<String, dynamic> json) {
|
||||||
return _Process.fromJson(json);
|
return _ProcessData.fromJson(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
mixin _$Process {
|
mixin _$ProcessData {
|
||||||
int get id => throw _privateConstructorUsedError;
|
int get id => throw _privateConstructorUsedError;
|
||||||
Metadata get metadata => throw _privateConstructorUsedError;
|
Metadata get metadata => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
/// Serializes this Process to a JSON map.
|
/// Serializes this ProcessData to a JSON map.
|
||||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
/// Create a copy of Process
|
/// Create a copy of ProcessData
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
$ProcessCopyWith<Process> get copyWith => throw _privateConstructorUsedError;
|
$ProcessDataCopyWith<ProcessData> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
abstract class $ProcessCopyWith<$Res> {
|
abstract class $ProcessDataCopyWith<$Res> {
|
||||||
factory $ProcessCopyWith(Process value, $Res Function(Process) then) =
|
factory $ProcessDataCopyWith(
|
||||||
_$ProcessCopyWithImpl<$Res, Process>;
|
ProcessData value, $Res Function(ProcessData) then) =
|
||||||
|
_$ProcessDataCopyWithImpl<$Res, ProcessData>;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({int id, Metadata metadata});
|
$Res call({int id, Metadata metadata});
|
||||||
|
|
||||||
@@ -2109,16 +2111,16 @@ abstract class $ProcessCopyWith<$Res> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
class _$ProcessCopyWithImpl<$Res, $Val extends Process>
|
class _$ProcessDataCopyWithImpl<$Res, $Val extends ProcessData>
|
||||||
implements $ProcessCopyWith<$Res> {
|
implements $ProcessDataCopyWith<$Res> {
|
||||||
_$ProcessCopyWithImpl(this._value, this._then);
|
_$ProcessDataCopyWithImpl(this._value, this._then);
|
||||||
|
|
||||||
// ignore: unused_field
|
// ignore: unused_field
|
||||||
final $Val _value;
|
final $Val _value;
|
||||||
// ignore: unused_field
|
// ignore: unused_field
|
||||||
final $Res Function($Val) _then;
|
final $Res Function($Val) _then;
|
||||||
|
|
||||||
/// Create a copy of Process
|
/// Create a copy of ProcessData
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@pragma('vm:prefer-inline')
|
@pragma('vm:prefer-inline')
|
||||||
@override
|
@override
|
||||||
@@ -2138,7 +2140,7 @@ class _$ProcessCopyWithImpl<$Res, $Val extends Process>
|
|||||||
) as $Val);
|
) as $Val);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a copy of Process
|
/// Create a copy of ProcessData
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@override
|
@override
|
||||||
@pragma('vm:prefer-inline')
|
@pragma('vm:prefer-inline')
|
||||||
@@ -2150,10 +2152,11 @@ class _$ProcessCopyWithImpl<$Res, $Val extends Process>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
abstract class _$$ProcessImplCopyWith<$Res> implements $ProcessCopyWith<$Res> {
|
abstract class _$$ProcessDataImplCopyWith<$Res>
|
||||||
factory _$$ProcessImplCopyWith(
|
implements $ProcessDataCopyWith<$Res> {
|
||||||
_$ProcessImpl value, $Res Function(_$ProcessImpl) then) =
|
factory _$$ProcessDataImplCopyWith(
|
||||||
__$$ProcessImplCopyWithImpl<$Res>;
|
_$ProcessDataImpl value, $Res Function(_$ProcessDataImpl) then) =
|
||||||
|
__$$ProcessDataImplCopyWithImpl<$Res>;
|
||||||
@override
|
@override
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({int id, Metadata metadata});
|
$Res call({int id, Metadata metadata});
|
||||||
@@ -2163,14 +2166,14 @@ abstract class _$$ProcessImplCopyWith<$Res> implements $ProcessCopyWith<$Res> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
class __$$ProcessImplCopyWithImpl<$Res>
|
class __$$ProcessDataImplCopyWithImpl<$Res>
|
||||||
extends _$ProcessCopyWithImpl<$Res, _$ProcessImpl>
|
extends _$ProcessDataCopyWithImpl<$Res, _$ProcessDataImpl>
|
||||||
implements _$$ProcessImplCopyWith<$Res> {
|
implements _$$ProcessDataImplCopyWith<$Res> {
|
||||||
__$$ProcessImplCopyWithImpl(
|
__$$ProcessDataImplCopyWithImpl(
|
||||||
_$ProcessImpl _value, $Res Function(_$ProcessImpl) _then)
|
_$ProcessDataImpl _value, $Res Function(_$ProcessDataImpl) _then)
|
||||||
: super(_value, _then);
|
: super(_value, _then);
|
||||||
|
|
||||||
/// Create a copy of Process
|
/// Create a copy of ProcessData
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@pragma('vm:prefer-inline')
|
@pragma('vm:prefer-inline')
|
||||||
@override
|
@override
|
||||||
@@ -2178,7 +2181,7 @@ class __$$ProcessImplCopyWithImpl<$Res>
|
|||||||
Object? id = null,
|
Object? id = null,
|
||||||
Object? metadata = null,
|
Object? metadata = null,
|
||||||
}) {
|
}) {
|
||||||
return _then(_$ProcessImpl(
|
return _then(_$ProcessDataImpl(
|
||||||
id: null == id
|
id: null == id
|
||||||
? _value.id
|
? _value.id
|
||||||
: id // ignore: cast_nullable_to_non_nullable
|
: id // ignore: cast_nullable_to_non_nullable
|
||||||
@@ -2193,11 +2196,11 @@ class __$$ProcessImplCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class _$ProcessImpl implements _Process {
|
class _$ProcessDataImpl implements _ProcessData {
|
||||||
const _$ProcessImpl({required this.id, required this.metadata});
|
const _$ProcessDataImpl({required this.id, required this.metadata});
|
||||||
|
|
||||||
factory _$ProcessImpl.fromJson(Map<String, dynamic> json) =>
|
factory _$ProcessDataImpl.fromJson(Map<String, dynamic> json) =>
|
||||||
_$$ProcessImplFromJson(json);
|
_$$ProcessDataImplFromJson(json);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final int id;
|
final int id;
|
||||||
@@ -2206,14 +2209,14 @@ class _$ProcessImpl implements _Process {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'Process(id: $id, metadata: $metadata)';
|
return 'ProcessData(id: $id, metadata: $metadata)';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) ||
|
return identical(this, other) ||
|
||||||
(other.runtimeType == runtimeType &&
|
(other.runtimeType == runtimeType &&
|
||||||
other is _$ProcessImpl &&
|
other is _$ProcessDataImpl &&
|
||||||
(identical(other.id, id) || other.id == id) &&
|
(identical(other.id, id) || other.id == id) &&
|
||||||
(identical(other.metadata, metadata) ||
|
(identical(other.metadata, metadata) ||
|
||||||
other.metadata == metadata));
|
other.metadata == metadata));
|
||||||
@@ -2223,39 +2226,40 @@ class _$ProcessImpl implements _Process {
|
|||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType, id, metadata);
|
int get hashCode => Object.hash(runtimeType, id, metadata);
|
||||||
|
|
||||||
/// Create a copy of Process
|
/// Create a copy of ProcessData
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@override
|
||||||
@pragma('vm:prefer-inline')
|
@pragma('vm:prefer-inline')
|
||||||
_$$ProcessImplCopyWith<_$ProcessImpl> get copyWith =>
|
_$$ProcessDataImplCopyWith<_$ProcessDataImpl> get copyWith =>
|
||||||
__$$ProcessImplCopyWithImpl<_$ProcessImpl>(this, _$identity);
|
__$$ProcessDataImplCopyWithImpl<_$ProcessDataImpl>(this, _$identity);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
return _$$ProcessImplToJson(
|
return _$$ProcessDataImplToJson(
|
||||||
this,
|
this,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class _Process implements Process {
|
abstract class _ProcessData implements ProcessData {
|
||||||
const factory _Process(
|
const factory _ProcessData(
|
||||||
{required final int id,
|
{required final int id,
|
||||||
required final Metadata metadata}) = _$ProcessImpl;
|
required final Metadata metadata}) = _$ProcessDataImpl;
|
||||||
|
|
||||||
factory _Process.fromJson(Map<String, dynamic> json) = _$ProcessImpl.fromJson;
|
factory _ProcessData.fromJson(Map<String, dynamic> json) =
|
||||||
|
_$ProcessDataImpl.fromJson;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get id;
|
int get id;
|
||||||
@override
|
@override
|
||||||
Metadata get metadata;
|
Metadata get metadata;
|
||||||
|
|
||||||
/// Create a copy of Process
|
/// Create a copy of ProcessData
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@override
|
@override
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
_$$ProcessImplCopyWith<_$ProcessImpl> get copyWith =>
|
_$$ProcessDataImplCopyWith<_$ProcessDataImpl> get copyWith =>
|
||||||
throw _privateConstructorUsedError;
|
throw _privateConstructorUsedError;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3424,3 +3428,185 @@ abstract class _TunProps implements TunProps {
|
|||||||
_$$TunPropsImplCopyWith<_$TunPropsImpl> get copyWith =>
|
_$$TunPropsImplCopyWith<_$TunPropsImpl> get copyWith =>
|
||||||
throw _privateConstructorUsedError;
|
throw _privateConstructorUsedError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Action _$ActionFromJson(Map<String, dynamic> json) {
|
||||||
|
return _Action.fromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$Action {
|
||||||
|
ActionMethod get method => throw _privateConstructorUsedError;
|
||||||
|
dynamic get data => throw _privateConstructorUsedError;
|
||||||
|
String get id => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
/// Serializes this Action to a JSON map.
|
||||||
|
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
/// Create a copy of Action
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
$ActionCopyWith<Action> get copyWith => throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class $ActionCopyWith<$Res> {
|
||||||
|
factory $ActionCopyWith(Action value, $Res Function(Action) then) =
|
||||||
|
_$ActionCopyWithImpl<$Res, Action>;
|
||||||
|
@useResult
|
||||||
|
$Res call({ActionMethod method, dynamic data, String id});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class _$ActionCopyWithImpl<$Res, $Val extends Action>
|
||||||
|
implements $ActionCopyWith<$Res> {
|
||||||
|
_$ActionCopyWithImpl(this._value, this._then);
|
||||||
|
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Val _value;
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Res Function($Val) _then;
|
||||||
|
|
||||||
|
/// Create a copy of Action
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? method = null,
|
||||||
|
Object? data = freezed,
|
||||||
|
Object? id = null,
|
||||||
|
}) {
|
||||||
|
return _then(_value.copyWith(
|
||||||
|
method: null == method
|
||||||
|
? _value.method
|
||||||
|
: method // ignore: cast_nullable_to_non_nullable
|
||||||
|
as ActionMethod,
|
||||||
|
data: freezed == data
|
||||||
|
? _value.data
|
||||||
|
: data // ignore: cast_nullable_to_non_nullable
|
||||||
|
as dynamic,
|
||||||
|
id: null == id
|
||||||
|
? _value.id
|
||||||
|
: id // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
) as $Val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class _$$ActionImplCopyWith<$Res> implements $ActionCopyWith<$Res> {
|
||||||
|
factory _$$ActionImplCopyWith(
|
||||||
|
_$ActionImpl value, $Res Function(_$ActionImpl) then) =
|
||||||
|
__$$ActionImplCopyWithImpl<$Res>;
|
||||||
|
@override
|
||||||
|
@useResult
|
||||||
|
$Res call({ActionMethod method, dynamic data, String id});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class __$$ActionImplCopyWithImpl<$Res>
|
||||||
|
extends _$ActionCopyWithImpl<$Res, _$ActionImpl>
|
||||||
|
implements _$$ActionImplCopyWith<$Res> {
|
||||||
|
__$$ActionImplCopyWithImpl(
|
||||||
|
_$ActionImpl _value, $Res Function(_$ActionImpl) _then)
|
||||||
|
: super(_value, _then);
|
||||||
|
|
||||||
|
/// Create a copy of Action
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? method = null,
|
||||||
|
Object? data = freezed,
|
||||||
|
Object? id = null,
|
||||||
|
}) {
|
||||||
|
return _then(_$ActionImpl(
|
||||||
|
method: null == method
|
||||||
|
? _value.method
|
||||||
|
: method // ignore: cast_nullable_to_non_nullable
|
||||||
|
as ActionMethod,
|
||||||
|
data: freezed == data
|
||||||
|
? _value.data
|
||||||
|
: data // ignore: cast_nullable_to_non_nullable
|
||||||
|
as dynamic,
|
||||||
|
id: null == id
|
||||||
|
? _value.id
|
||||||
|
: id // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
class _$ActionImpl implements _Action {
|
||||||
|
const _$ActionImpl(
|
||||||
|
{required this.method, required this.data, required this.id});
|
||||||
|
|
||||||
|
factory _$ActionImpl.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$$ActionImplFromJson(json);
|
||||||
|
|
||||||
|
@override
|
||||||
|
final ActionMethod method;
|
||||||
|
@override
|
||||||
|
final dynamic data;
|
||||||
|
@override
|
||||||
|
final String id;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'Action(method: $method, data: $data, id: $id)';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) ||
|
||||||
|
(other.runtimeType == runtimeType &&
|
||||||
|
other is _$ActionImpl &&
|
||||||
|
(identical(other.method, method) || other.method == method) &&
|
||||||
|
const DeepCollectionEquality().equals(other.data, data) &&
|
||||||
|
(identical(other.id, id) || other.id == id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(
|
||||||
|
runtimeType, method, const DeepCollectionEquality().hash(data), id);
|
||||||
|
|
||||||
|
/// Create a copy of Action
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$$ActionImplCopyWith<_$ActionImpl> get copyWith =>
|
||||||
|
__$$ActionImplCopyWithImpl<_$ActionImpl>(this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$$ActionImplToJson(
|
||||||
|
this,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _Action implements Action {
|
||||||
|
const factory _Action(
|
||||||
|
{required final ActionMethod method,
|
||||||
|
required final dynamic data,
|
||||||
|
required final String id}) = _$ActionImpl;
|
||||||
|
|
||||||
|
factory _Action.fromJson(Map<String, dynamic> json) = _$ActionImpl.fromJson;
|
||||||
|
|
||||||
|
@override
|
||||||
|
ActionMethod get method;
|
||||||
|
@override
|
||||||
|
dynamic get data;
|
||||||
|
@override
|
||||||
|
String get id;
|
||||||
|
|
||||||
|
/// Create a copy of Action
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
_$$ActionImplCopyWith<_$ActionImpl> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
part of '../ffi.dart';
|
part of '../core.dart';
|
||||||
|
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
// JsonSerializableGenerator
|
// JsonSerializableGenerator
|
||||||
@@ -188,13 +188,13 @@ Map<String, dynamic> _$$NowImplToJson(_$NowImpl instance) => <String, dynamic>{
|
|||||||
'value': instance.value,
|
'value': instance.value,
|
||||||
};
|
};
|
||||||
|
|
||||||
_$ProcessImpl _$$ProcessImplFromJson(Map<String, dynamic> json) =>
|
_$ProcessDataImpl _$$ProcessDataImplFromJson(Map<String, dynamic> json) =>
|
||||||
_$ProcessImpl(
|
_$ProcessDataImpl(
|
||||||
id: (json['id'] as num).toInt(),
|
id: (json['id'] as num).toInt(),
|
||||||
metadata: Metadata.fromJson(json['metadata'] as Map<String, dynamic>),
|
metadata: Metadata.fromJson(json['metadata'] as Map<String, dynamic>),
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$$ProcessImplToJson(_$ProcessImpl instance) =>
|
Map<String, dynamic> _$$ProcessDataImplToJson(_$ProcessDataImpl instance) =>
|
||||||
<String, dynamic>{
|
<String, dynamic>{
|
||||||
'id': instance.id,
|
'id': instance.id,
|
||||||
'metadata': instance.metadata,
|
'metadata': instance.metadata,
|
||||||
@@ -289,3 +289,44 @@ Map<String, dynamic> _$$TunPropsImplToJson(_$TunPropsImpl instance) =>
|
|||||||
'dns': instance.dns,
|
'dns': instance.dns,
|
||||||
'dns6': instance.dns6,
|
'dns6': instance.dns6,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_$ActionImpl _$$ActionImplFromJson(Map<String, dynamic> json) => _$ActionImpl(
|
||||||
|
method: $enumDecode(_$ActionMethodEnumMap, json['method']),
|
||||||
|
data: json['data'],
|
||||||
|
id: json['id'] as String,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$$ActionImplToJson(_$ActionImpl instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'method': _$ActionMethodEnumMap[instance.method]!,
|
||||||
|
'data': instance.data,
|
||||||
|
'id': instance.id,
|
||||||
|
};
|
||||||
|
|
||||||
|
const _$ActionMethodEnumMap = {
|
||||||
|
ActionMethod.message: 'message',
|
||||||
|
ActionMethod.initClash: 'initClash',
|
||||||
|
ActionMethod.getIsInit: 'getIsInit',
|
||||||
|
ActionMethod.forceGc: 'forceGc',
|
||||||
|
ActionMethod.shutdown: 'shutdown',
|
||||||
|
ActionMethod.validateConfig: 'validateConfig',
|
||||||
|
ActionMethod.updateConfig: 'updateConfig',
|
||||||
|
ActionMethod.getProxies: 'getProxies',
|
||||||
|
ActionMethod.changeProxy: 'changeProxy',
|
||||||
|
ActionMethod.getTraffic: 'getTraffic',
|
||||||
|
ActionMethod.getTotalTraffic: 'getTotalTraffic',
|
||||||
|
ActionMethod.resetTraffic: 'resetTraffic',
|
||||||
|
ActionMethod.asyncTestDelay: 'asyncTestDelay',
|
||||||
|
ActionMethod.getConnections: 'getConnections',
|
||||||
|
ActionMethod.closeConnections: 'closeConnections',
|
||||||
|
ActionMethod.closeConnection: 'closeConnection',
|
||||||
|
ActionMethod.getExternalProviders: 'getExternalProviders',
|
||||||
|
ActionMethod.getExternalProvider: 'getExternalProvider',
|
||||||
|
ActionMethod.updateGeoData: 'updateGeoData',
|
||||||
|
ActionMethod.updateExternalProvider: 'updateExternalProvider',
|
||||||
|
ActionMethod.sideLoadExternalProvider: 'sideLoadExternalProvider',
|
||||||
|
ActionMethod.startLog: 'startLog',
|
||||||
|
ActionMethod.stopLog: 'stopLog',
|
||||||
|
ActionMethod.startListener: 'startListener',
|
||||||
|
ActionMethod.stopListener: 'stopListener',
|
||||||
|
};
|
||||||
@@ -1046,13 +1046,15 @@ abstract class _ApplicationSelectorState implements ApplicationSelectorState {
|
|||||||
/// @nodoc
|
/// @nodoc
|
||||||
mixin _$TrayState {
|
mixin _$TrayState {
|
||||||
Mode get mode => throw _privateConstructorUsedError;
|
Mode get mode => throw _privateConstructorUsedError;
|
||||||
|
int get port => throw _privateConstructorUsedError;
|
||||||
bool get autoLaunch => throw _privateConstructorUsedError;
|
bool get autoLaunch => throw _privateConstructorUsedError;
|
||||||
bool get adminAutoLaunch => throw _privateConstructorUsedError;
|
|
||||||
bool get systemProxy => throw _privateConstructorUsedError;
|
bool get systemProxy => throw _privateConstructorUsedError;
|
||||||
bool get tunEnable => throw _privateConstructorUsedError;
|
bool get tunEnable => throw _privateConstructorUsedError;
|
||||||
bool get isStart => throw _privateConstructorUsedError;
|
bool get isStart => throw _privateConstructorUsedError;
|
||||||
String? get locale => throw _privateConstructorUsedError;
|
String? get locale => throw _privateConstructorUsedError;
|
||||||
Brightness? get brightness => throw _privateConstructorUsedError;
|
Brightness? get brightness => throw _privateConstructorUsedError;
|
||||||
|
List<Group> get groups => throw _privateConstructorUsedError;
|
||||||
|
Map<String, String> get map => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
/// Create a copy of TrayState
|
/// Create a copy of TrayState
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@@ -1068,13 +1070,15 @@ abstract class $TrayStateCopyWith<$Res> {
|
|||||||
@useResult
|
@useResult
|
||||||
$Res call(
|
$Res call(
|
||||||
{Mode mode,
|
{Mode mode,
|
||||||
|
int port,
|
||||||
bool autoLaunch,
|
bool autoLaunch,
|
||||||
bool adminAutoLaunch,
|
|
||||||
bool systemProxy,
|
bool systemProxy,
|
||||||
bool tunEnable,
|
bool tunEnable,
|
||||||
bool isStart,
|
bool isStart,
|
||||||
String? locale,
|
String? locale,
|
||||||
Brightness? brightness});
|
Brightness? brightness,
|
||||||
|
List<Group> groups,
|
||||||
|
Map<String, String> map});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@@ -1093,27 +1097,29 @@ class _$TrayStateCopyWithImpl<$Res, $Val extends TrayState>
|
|||||||
@override
|
@override
|
||||||
$Res call({
|
$Res call({
|
||||||
Object? mode = null,
|
Object? mode = null,
|
||||||
|
Object? port = null,
|
||||||
Object? autoLaunch = null,
|
Object? autoLaunch = null,
|
||||||
Object? adminAutoLaunch = null,
|
|
||||||
Object? systemProxy = null,
|
Object? systemProxy = null,
|
||||||
Object? tunEnable = null,
|
Object? tunEnable = null,
|
||||||
Object? isStart = null,
|
Object? isStart = null,
|
||||||
Object? locale = freezed,
|
Object? locale = freezed,
|
||||||
Object? brightness = freezed,
|
Object? brightness = freezed,
|
||||||
|
Object? groups = null,
|
||||||
|
Object? map = null,
|
||||||
}) {
|
}) {
|
||||||
return _then(_value.copyWith(
|
return _then(_value.copyWith(
|
||||||
mode: null == mode
|
mode: null == mode
|
||||||
? _value.mode
|
? _value.mode
|
||||||
: mode // ignore: cast_nullable_to_non_nullable
|
: mode // ignore: cast_nullable_to_non_nullable
|
||||||
as Mode,
|
as Mode,
|
||||||
|
port: null == port
|
||||||
|
? _value.port
|
||||||
|
: port // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
autoLaunch: null == autoLaunch
|
autoLaunch: null == autoLaunch
|
||||||
? _value.autoLaunch
|
? _value.autoLaunch
|
||||||
: autoLaunch // ignore: cast_nullable_to_non_nullable
|
: autoLaunch // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,
|
as bool,
|
||||||
adminAutoLaunch: null == adminAutoLaunch
|
|
||||||
? _value.adminAutoLaunch
|
|
||||||
: adminAutoLaunch // ignore: cast_nullable_to_non_nullable
|
|
||||||
as bool,
|
|
||||||
systemProxy: null == systemProxy
|
systemProxy: null == systemProxy
|
||||||
? _value.systemProxy
|
? _value.systemProxy
|
||||||
: systemProxy // ignore: cast_nullable_to_non_nullable
|
: systemProxy // ignore: cast_nullable_to_non_nullable
|
||||||
@@ -1134,6 +1140,14 @@ class _$TrayStateCopyWithImpl<$Res, $Val extends TrayState>
|
|||||||
? _value.brightness
|
? _value.brightness
|
||||||
: brightness // ignore: cast_nullable_to_non_nullable
|
: brightness // ignore: cast_nullable_to_non_nullable
|
||||||
as Brightness?,
|
as Brightness?,
|
||||||
|
groups: null == groups
|
||||||
|
? _value.groups
|
||||||
|
: groups // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<Group>,
|
||||||
|
map: null == map
|
||||||
|
? _value.map
|
||||||
|
: map // ignore: cast_nullable_to_non_nullable
|
||||||
|
as Map<String, String>,
|
||||||
) as $Val);
|
) as $Val);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1148,13 +1162,15 @@ abstract class _$$TrayStateImplCopyWith<$Res>
|
|||||||
@useResult
|
@useResult
|
||||||
$Res call(
|
$Res call(
|
||||||
{Mode mode,
|
{Mode mode,
|
||||||
|
int port,
|
||||||
bool autoLaunch,
|
bool autoLaunch,
|
||||||
bool adminAutoLaunch,
|
|
||||||
bool systemProxy,
|
bool systemProxy,
|
||||||
bool tunEnable,
|
bool tunEnable,
|
||||||
bool isStart,
|
bool isStart,
|
||||||
String? locale,
|
String? locale,
|
||||||
Brightness? brightness});
|
Brightness? brightness,
|
||||||
|
List<Group> groups,
|
||||||
|
Map<String, String> map});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@@ -1171,27 +1187,29 @@ class __$$TrayStateImplCopyWithImpl<$Res>
|
|||||||
@override
|
@override
|
||||||
$Res call({
|
$Res call({
|
||||||
Object? mode = null,
|
Object? mode = null,
|
||||||
|
Object? port = null,
|
||||||
Object? autoLaunch = null,
|
Object? autoLaunch = null,
|
||||||
Object? adminAutoLaunch = null,
|
|
||||||
Object? systemProxy = null,
|
Object? systemProxy = null,
|
||||||
Object? tunEnable = null,
|
Object? tunEnable = null,
|
||||||
Object? isStart = null,
|
Object? isStart = null,
|
||||||
Object? locale = freezed,
|
Object? locale = freezed,
|
||||||
Object? brightness = freezed,
|
Object? brightness = freezed,
|
||||||
|
Object? groups = null,
|
||||||
|
Object? map = null,
|
||||||
}) {
|
}) {
|
||||||
return _then(_$TrayStateImpl(
|
return _then(_$TrayStateImpl(
|
||||||
mode: null == mode
|
mode: null == mode
|
||||||
? _value.mode
|
? _value.mode
|
||||||
: mode // ignore: cast_nullable_to_non_nullable
|
: mode // ignore: cast_nullable_to_non_nullable
|
||||||
as Mode,
|
as Mode,
|
||||||
|
port: null == port
|
||||||
|
? _value.port
|
||||||
|
: port // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
autoLaunch: null == autoLaunch
|
autoLaunch: null == autoLaunch
|
||||||
? _value.autoLaunch
|
? _value.autoLaunch
|
||||||
: autoLaunch // ignore: cast_nullable_to_non_nullable
|
: autoLaunch // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,
|
as bool,
|
||||||
adminAutoLaunch: null == adminAutoLaunch
|
|
||||||
? _value.adminAutoLaunch
|
|
||||||
: adminAutoLaunch // ignore: cast_nullable_to_non_nullable
|
|
||||||
as bool,
|
|
||||||
systemProxy: null == systemProxy
|
systemProxy: null == systemProxy
|
||||||
? _value.systemProxy
|
? _value.systemProxy
|
||||||
: systemProxy // ignore: cast_nullable_to_non_nullable
|
: systemProxy // ignore: cast_nullable_to_non_nullable
|
||||||
@@ -1212,6 +1230,14 @@ class __$$TrayStateImplCopyWithImpl<$Res>
|
|||||||
? _value.brightness
|
? _value.brightness
|
||||||
: brightness // ignore: cast_nullable_to_non_nullable
|
: brightness // ignore: cast_nullable_to_non_nullable
|
||||||
as Brightness?,
|
as Brightness?,
|
||||||
|
groups: null == groups
|
||||||
|
? _value._groups
|
||||||
|
: groups // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<Group>,
|
||||||
|
map: null == map
|
||||||
|
? _value._map
|
||||||
|
: map // ignore: cast_nullable_to_non_nullable
|
||||||
|
as Map<String, String>,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1221,20 +1247,24 @@ class __$$TrayStateImplCopyWithImpl<$Res>
|
|||||||
class _$TrayStateImpl implements _TrayState {
|
class _$TrayStateImpl implements _TrayState {
|
||||||
const _$TrayStateImpl(
|
const _$TrayStateImpl(
|
||||||
{required this.mode,
|
{required this.mode,
|
||||||
|
required this.port,
|
||||||
required this.autoLaunch,
|
required this.autoLaunch,
|
||||||
required this.adminAutoLaunch,
|
|
||||||
required this.systemProxy,
|
required this.systemProxy,
|
||||||
required this.tunEnable,
|
required this.tunEnable,
|
||||||
required this.isStart,
|
required this.isStart,
|
||||||
required this.locale,
|
required this.locale,
|
||||||
required this.brightness});
|
required this.brightness,
|
||||||
|
required final List<Group> groups,
|
||||||
|
required final Map<String, String> map})
|
||||||
|
: _groups = groups,
|
||||||
|
_map = map;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final Mode mode;
|
final Mode mode;
|
||||||
@override
|
@override
|
||||||
final bool autoLaunch;
|
final int port;
|
||||||
@override
|
@override
|
||||||
final bool adminAutoLaunch;
|
final bool autoLaunch;
|
||||||
@override
|
@override
|
||||||
final bool systemProxy;
|
final bool systemProxy;
|
||||||
@override
|
@override
|
||||||
@@ -1245,10 +1275,25 @@ class _$TrayStateImpl implements _TrayState {
|
|||||||
final String? locale;
|
final String? locale;
|
||||||
@override
|
@override
|
||||||
final Brightness? brightness;
|
final Brightness? brightness;
|
||||||
|
final List<Group> _groups;
|
||||||
|
@override
|
||||||
|
List<Group> get groups {
|
||||||
|
if (_groups is EqualUnmodifiableListView) return _groups;
|
||||||
|
// ignore: implicit_dynamic_type
|
||||||
|
return EqualUnmodifiableListView(_groups);
|
||||||
|
}
|
||||||
|
|
||||||
|
final Map<String, String> _map;
|
||||||
|
@override
|
||||||
|
Map<String, String> get map {
|
||||||
|
if (_map is EqualUnmodifiableMapView) return _map;
|
||||||
|
// ignore: implicit_dynamic_type
|
||||||
|
return EqualUnmodifiableMapView(_map);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'TrayState(mode: $mode, autoLaunch: $autoLaunch, adminAutoLaunch: $adminAutoLaunch, systemProxy: $systemProxy, tunEnable: $tunEnable, isStart: $isStart, locale: $locale, brightness: $brightness)';
|
return 'TrayState(mode: $mode, port: $port, autoLaunch: $autoLaunch, systemProxy: $systemProxy, tunEnable: $tunEnable, isStart: $isStart, locale: $locale, brightness: $brightness, groups: $groups, map: $map)';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -1257,10 +1302,9 @@ class _$TrayStateImpl implements _TrayState {
|
|||||||
(other.runtimeType == runtimeType &&
|
(other.runtimeType == runtimeType &&
|
||||||
other is _$TrayStateImpl &&
|
other is _$TrayStateImpl &&
|
||||||
(identical(other.mode, mode) || other.mode == mode) &&
|
(identical(other.mode, mode) || other.mode == mode) &&
|
||||||
|
(identical(other.port, port) || other.port == port) &&
|
||||||
(identical(other.autoLaunch, autoLaunch) ||
|
(identical(other.autoLaunch, autoLaunch) ||
|
||||||
other.autoLaunch == autoLaunch) &&
|
other.autoLaunch == autoLaunch) &&
|
||||||
(identical(other.adminAutoLaunch, adminAutoLaunch) ||
|
|
||||||
other.adminAutoLaunch == adminAutoLaunch) &&
|
|
||||||
(identical(other.systemProxy, systemProxy) ||
|
(identical(other.systemProxy, systemProxy) ||
|
||||||
other.systemProxy == systemProxy) &&
|
other.systemProxy == systemProxy) &&
|
||||||
(identical(other.tunEnable, tunEnable) ||
|
(identical(other.tunEnable, tunEnable) ||
|
||||||
@@ -1268,12 +1312,24 @@ class _$TrayStateImpl implements _TrayState {
|
|||||||
(identical(other.isStart, isStart) || other.isStart == isStart) &&
|
(identical(other.isStart, isStart) || other.isStart == isStart) &&
|
||||||
(identical(other.locale, locale) || other.locale == locale) &&
|
(identical(other.locale, locale) || other.locale == locale) &&
|
||||||
(identical(other.brightness, brightness) ||
|
(identical(other.brightness, brightness) ||
|
||||||
other.brightness == brightness));
|
other.brightness == brightness) &&
|
||||||
|
const DeepCollectionEquality().equals(other._groups, _groups) &&
|
||||||
|
const DeepCollectionEquality().equals(other._map, _map));
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType, mode, autoLaunch,
|
int get hashCode => Object.hash(
|
||||||
adminAutoLaunch, systemProxy, tunEnable, isStart, locale, brightness);
|
runtimeType,
|
||||||
|
mode,
|
||||||
|
port,
|
||||||
|
autoLaunch,
|
||||||
|
systemProxy,
|
||||||
|
tunEnable,
|
||||||
|
isStart,
|
||||||
|
locale,
|
||||||
|
brightness,
|
||||||
|
const DeepCollectionEquality().hash(_groups),
|
||||||
|
const DeepCollectionEquality().hash(_map));
|
||||||
|
|
||||||
/// Create a copy of TrayState
|
/// Create a copy of TrayState
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@@ -1287,20 +1343,22 @@ class _$TrayStateImpl implements _TrayState {
|
|||||||
abstract class _TrayState implements TrayState {
|
abstract class _TrayState implements TrayState {
|
||||||
const factory _TrayState(
|
const factory _TrayState(
|
||||||
{required final Mode mode,
|
{required final Mode mode,
|
||||||
|
required final int port,
|
||||||
required final bool autoLaunch,
|
required final bool autoLaunch,
|
||||||
required final bool adminAutoLaunch,
|
|
||||||
required final bool systemProxy,
|
required final bool systemProxy,
|
||||||
required final bool tunEnable,
|
required final bool tunEnable,
|
||||||
required final bool isStart,
|
required final bool isStart,
|
||||||
required final String? locale,
|
required final String? locale,
|
||||||
required final Brightness? brightness}) = _$TrayStateImpl;
|
required final Brightness? brightness,
|
||||||
|
required final List<Group> groups,
|
||||||
|
required final Map<String, String> map}) = _$TrayStateImpl;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Mode get mode;
|
Mode get mode;
|
||||||
@override
|
@override
|
||||||
bool get autoLaunch;
|
int get port;
|
||||||
@override
|
@override
|
||||||
bool get adminAutoLaunch;
|
bool get autoLaunch;
|
||||||
@override
|
@override
|
||||||
bool get systemProxy;
|
bool get systemProxy;
|
||||||
@override
|
@override
|
||||||
@@ -1311,6 +1369,10 @@ abstract class _TrayState implements TrayState {
|
|||||||
String? get locale;
|
String? get locale;
|
||||||
@override
|
@override
|
||||||
Brightness? get brightness;
|
Brightness? get brightness;
|
||||||
|
@override
|
||||||
|
List<Group> get groups;
|
||||||
|
@override
|
||||||
|
Map<String, String> get map;
|
||||||
|
|
||||||
/// Create a copy of TrayState
|
/// Create a copy of TrayState
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@@ -3150,7 +3212,6 @@ abstract class _ProxiesActionsState implements ProxiesActionsState {
|
|||||||
/// @nodoc
|
/// @nodoc
|
||||||
mixin _$AutoLaunchState {
|
mixin _$AutoLaunchState {
|
||||||
bool get isAutoLaunch => throw _privateConstructorUsedError;
|
bool get isAutoLaunch => throw _privateConstructorUsedError;
|
||||||
bool get isAdminAutoLaunch => throw _privateConstructorUsedError;
|
|
||||||
|
|
||||||
/// Create a copy of AutoLaunchState
|
/// Create a copy of AutoLaunchState
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@@ -3165,7 +3226,7 @@ abstract class $AutoLaunchStateCopyWith<$Res> {
|
|||||||
AutoLaunchState value, $Res Function(AutoLaunchState) then) =
|
AutoLaunchState value, $Res Function(AutoLaunchState) then) =
|
||||||
_$AutoLaunchStateCopyWithImpl<$Res, AutoLaunchState>;
|
_$AutoLaunchStateCopyWithImpl<$Res, AutoLaunchState>;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({bool isAutoLaunch, bool isAdminAutoLaunch});
|
$Res call({bool isAutoLaunch});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@@ -3184,17 +3245,12 @@ class _$AutoLaunchStateCopyWithImpl<$Res, $Val extends AutoLaunchState>
|
|||||||
@override
|
@override
|
||||||
$Res call({
|
$Res call({
|
||||||
Object? isAutoLaunch = null,
|
Object? isAutoLaunch = null,
|
||||||
Object? isAdminAutoLaunch = null,
|
|
||||||
}) {
|
}) {
|
||||||
return _then(_value.copyWith(
|
return _then(_value.copyWith(
|
||||||
isAutoLaunch: null == isAutoLaunch
|
isAutoLaunch: null == isAutoLaunch
|
||||||
? _value.isAutoLaunch
|
? _value.isAutoLaunch
|
||||||
: isAutoLaunch // ignore: cast_nullable_to_non_nullable
|
: isAutoLaunch // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,
|
as bool,
|
||||||
isAdminAutoLaunch: null == isAdminAutoLaunch
|
|
||||||
? _value.isAdminAutoLaunch
|
|
||||||
: isAdminAutoLaunch // ignore: cast_nullable_to_non_nullable
|
|
||||||
as bool,
|
|
||||||
) as $Val);
|
) as $Val);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3207,7 +3263,7 @@ abstract class _$$AutoLaunchStateImplCopyWith<$Res>
|
|||||||
__$$AutoLaunchStateImplCopyWithImpl<$Res>;
|
__$$AutoLaunchStateImplCopyWithImpl<$Res>;
|
||||||
@override
|
@override
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({bool isAutoLaunch, bool isAdminAutoLaunch});
|
$Res call({bool isAutoLaunch});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@@ -3224,17 +3280,12 @@ class __$$AutoLaunchStateImplCopyWithImpl<$Res>
|
|||||||
@override
|
@override
|
||||||
$Res call({
|
$Res call({
|
||||||
Object? isAutoLaunch = null,
|
Object? isAutoLaunch = null,
|
||||||
Object? isAdminAutoLaunch = null,
|
|
||||||
}) {
|
}) {
|
||||||
return _then(_$AutoLaunchStateImpl(
|
return _then(_$AutoLaunchStateImpl(
|
||||||
isAutoLaunch: null == isAutoLaunch
|
isAutoLaunch: null == isAutoLaunch
|
||||||
? _value.isAutoLaunch
|
? _value.isAutoLaunch
|
||||||
: isAutoLaunch // ignore: cast_nullable_to_non_nullable
|
: isAutoLaunch // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,
|
as bool,
|
||||||
isAdminAutoLaunch: null == isAdminAutoLaunch
|
|
||||||
? _value.isAdminAutoLaunch
|
|
||||||
: isAdminAutoLaunch // ignore: cast_nullable_to_non_nullable
|
|
||||||
as bool,
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3242,17 +3293,14 @@ class __$$AutoLaunchStateImplCopyWithImpl<$Res>
|
|||||||
/// @nodoc
|
/// @nodoc
|
||||||
|
|
||||||
class _$AutoLaunchStateImpl implements _AutoLaunchState {
|
class _$AutoLaunchStateImpl implements _AutoLaunchState {
|
||||||
const _$AutoLaunchStateImpl(
|
const _$AutoLaunchStateImpl({required this.isAutoLaunch});
|
||||||
{required this.isAutoLaunch, required this.isAdminAutoLaunch});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final bool isAutoLaunch;
|
final bool isAutoLaunch;
|
||||||
@override
|
|
||||||
final bool isAdminAutoLaunch;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'AutoLaunchState(isAutoLaunch: $isAutoLaunch, isAdminAutoLaunch: $isAdminAutoLaunch)';
|
return 'AutoLaunchState(isAutoLaunch: $isAutoLaunch)';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -3261,13 +3309,11 @@ class _$AutoLaunchStateImpl implements _AutoLaunchState {
|
|||||||
(other.runtimeType == runtimeType &&
|
(other.runtimeType == runtimeType &&
|
||||||
other is _$AutoLaunchStateImpl &&
|
other is _$AutoLaunchStateImpl &&
|
||||||
(identical(other.isAutoLaunch, isAutoLaunch) ||
|
(identical(other.isAutoLaunch, isAutoLaunch) ||
|
||||||
other.isAutoLaunch == isAutoLaunch) &&
|
other.isAutoLaunch == isAutoLaunch));
|
||||||
(identical(other.isAdminAutoLaunch, isAdminAutoLaunch) ||
|
|
||||||
other.isAdminAutoLaunch == isAdminAutoLaunch));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType, isAutoLaunch, isAdminAutoLaunch);
|
int get hashCode => Object.hash(runtimeType, isAutoLaunch);
|
||||||
|
|
||||||
/// Create a copy of AutoLaunchState
|
/// Create a copy of AutoLaunchState
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@@ -3280,14 +3326,11 @@ class _$AutoLaunchStateImpl implements _AutoLaunchState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
abstract class _AutoLaunchState implements AutoLaunchState {
|
abstract class _AutoLaunchState implements AutoLaunchState {
|
||||||
const factory _AutoLaunchState(
|
const factory _AutoLaunchState({required final bool isAutoLaunch}) =
|
||||||
{required final bool isAutoLaunch,
|
_$AutoLaunchStateImpl;
|
||||||
required final bool isAdminAutoLaunch}) = _$AutoLaunchStateImpl;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool get isAutoLaunch;
|
bool get isAutoLaunch;
|
||||||
@override
|
|
||||||
bool get isAdminAutoLaunch;
|
|
||||||
|
|
||||||
/// Create a copy of AutoLaunchState
|
/// Create a copy of AutoLaunchState
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export 'app.dart';
|
export 'app.dart';
|
||||||
export 'clash_config.dart';
|
export 'clash_config.dart';
|
||||||
export 'config.dart';
|
|
||||||
export 'profile.dart';
|
|
||||||
export 'ffi.dart';
|
|
||||||
export 'selector.dart';
|
|
||||||
export 'common.dart';
|
export 'common.dart';
|
||||||
|
export 'config.dart';
|
||||||
|
export 'core.dart';
|
||||||
|
export 'profile.dart';
|
||||||
|
export 'selector.dart';
|
||||||
|
|||||||
@@ -96,6 +96,21 @@ extension ProfileExtension on Profile {
|
|||||||
return await File(profilePath!).exists();
|
return await File(profilePath!).exists();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<File> getFile() async {
|
||||||
|
final path = await appPath.getProfilePath(id);
|
||||||
|
final file = File(path!);
|
||||||
|
final isExists = await file.exists();
|
||||||
|
if (!isExists) {
|
||||||
|
await file.create(recursive: true);
|
||||||
|
}
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<int> get profileLastModified async {
|
||||||
|
final file = await getFile();
|
||||||
|
return (await file.lastModified()).microsecondsSinceEpoch;
|
||||||
|
}
|
||||||
|
|
||||||
Future<Profile> update() async {
|
Future<Profile> update() async {
|
||||||
final response = await request.getFileResponseForUrl(url);
|
final response = await request.getFileResponseForUrl(url);
|
||||||
final disposition = response.headers.value("content-disposition");
|
final disposition = response.headers.value("content-disposition");
|
||||||
@@ -111,12 +126,7 @@ extension ProfileExtension on Profile {
|
|||||||
if (message.isNotEmpty) {
|
if (message.isNotEmpty) {
|
||||||
throw message;
|
throw message;
|
||||||
}
|
}
|
||||||
final path = await appPath.getProfilePath(id);
|
final file = await getFile();
|
||||||
final file = File(path!);
|
|
||||||
final isExists = await file.exists();
|
|
||||||
if (!isExists) {
|
|
||||||
await file.create(recursive: true);
|
|
||||||
}
|
|
||||||
await file.writeAsBytes(bytes);
|
await file.writeAsBytes(bytes);
|
||||||
return copyWith(lastUpdateDate: DateTime.now());
|
return copyWith(lastUpdateDate: DateTime.now());
|
||||||
}
|
}
|
||||||
@@ -126,12 +136,7 @@ extension ProfileExtension on Profile {
|
|||||||
if (message.isNotEmpty) {
|
if (message.isNotEmpty) {
|
||||||
throw message;
|
throw message;
|
||||||
}
|
}
|
||||||
final path = await appPath.getProfilePath(id);
|
final file = await getFile();
|
||||||
final file = File(path!);
|
|
||||||
final isExists = await file.exists();
|
|
||||||
if (!isExists) {
|
|
||||||
await file.create(recursive: true);
|
|
||||||
}
|
|
||||||
await file.writeAsString(value);
|
await file.writeAsString(value);
|
||||||
return copyWith(lastUpdateDate: DateTime.now());
|
return copyWith(lastUpdateDate: DateTime.now());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,13 +63,15 @@ class ApplicationSelectorState with _$ApplicationSelectorState {
|
|||||||
class TrayState with _$TrayState {
|
class TrayState with _$TrayState {
|
||||||
const factory TrayState({
|
const factory TrayState({
|
||||||
required Mode mode,
|
required Mode mode,
|
||||||
|
required int port,
|
||||||
required bool autoLaunch,
|
required bool autoLaunch,
|
||||||
required bool adminAutoLaunch,
|
|
||||||
required bool systemProxy,
|
required bool systemProxy,
|
||||||
required bool tunEnable,
|
required bool tunEnable,
|
||||||
required bool isStart,
|
required bool isStart,
|
||||||
required String? locale,
|
required String? locale,
|
||||||
required Brightness? brightness,
|
required Brightness? brightness,
|
||||||
|
required List<Group> groups,
|
||||||
|
required SelectedMap map,
|
||||||
}) = _TrayState;
|
}) = _TrayState;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,7 +199,6 @@ class ProxiesActionsState with _$ProxiesActionsState {
|
|||||||
class AutoLaunchState with _$AutoLaunchState {
|
class AutoLaunchState with _$AutoLaunchState {
|
||||||
const factory AutoLaunchState({
|
const factory AutoLaunchState({
|
||||||
required bool isAutoLaunch,
|
required bool isAutoLaunch,
|
||||||
required bool isAdminAutoLaunch,
|
|
||||||
}) = _AutoLaunchState;
|
}) = _AutoLaunchState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ class Vpn {
|
|||||||
clashCore.requestGc();
|
clashCore.requestGc();
|
||||||
case "dnsChanged":
|
case "dnsChanged":
|
||||||
final dns = call.arguments as String;
|
final dns = call.arguments as String;
|
||||||
clashCore.updateDns(dns);
|
clashLib?.updateDns(dns);
|
||||||
default:
|
default:
|
||||||
throw MissingPluginException();
|
throw MissingPluginException();
|
||||||
}
|
}
|
||||||
@@ -40,7 +40,7 @@ class Vpn {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<bool?> startVpn() async {
|
Future<bool?> startVpn() async {
|
||||||
final options = clashCore.getAndroidVpnOptions();
|
final options = clashLib?.getAndroidVpnOptions();
|
||||||
return await methodChannel.invokeMethod<bool>("start", {
|
return await methodChannel.invokeMethod<bool>("start", {
|
||||||
'data': json.encode(options),
|
'data': json.encode(options),
|
||||||
});
|
});
|
||||||
@@ -54,7 +54,7 @@ class Vpn {
|
|||||||
return await methodChannel.invokeMethod<bool?>("setProtect", {'fd': fd});
|
return await methodChannel.invokeMethod<bool?>("setProtect", {'fd': fd});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String?> resolverProcess(Process process) async {
|
Future<String?> resolverProcess(ProcessData process) async {
|
||||||
return await methodChannel.invokeMethod<String>("resolverProcess", {
|
return await methodChannel.invokeMethod<String>("resolverProcess", {
|
||||||
"data": json.encode(process),
|
"data": json.encode(process),
|
||||||
});
|
});
|
||||||
@@ -79,7 +79,7 @@ class Vpn {
|
|||||||
receiver!.listen((message) {
|
receiver!.listen((message) {
|
||||||
_handleServiceMessage(message);
|
_handleServiceMessage(message);
|
||||||
});
|
});
|
||||||
clashCore.startTun(fd, receiver!.sendPort.nativePort);
|
clashLib?.startTun(fd, receiver!.sendPort.nativePort);
|
||||||
}
|
}
|
||||||
|
|
||||||
setServiceMessageHandler(ServiceMessageListener serviceMessageListener) {
|
setServiceMessageHandler(ServiceMessageListener serviceMessageListener) {
|
||||||
@@ -92,7 +92,7 @@ class Vpn {
|
|||||||
case ServiceMessageType.protect:
|
case ServiceMessageType.protect:
|
||||||
_serviceMessageHandler?.onProtect(Fd.fromJson(m.data));
|
_serviceMessageHandler?.onProtect(Fd.fromJson(m.data));
|
||||||
case ServiceMessageType.process:
|
case ServiceMessageType.process:
|
||||||
_serviceMessageHandler?.onProcess(Process.fromJson(m.data));
|
_serviceMessageHandler?.onProcess(ProcessData.fromJson(m.data));
|
||||||
case ServiceMessageType.started:
|
case ServiceMessageType.started:
|
||||||
_serviceMessageHandler?.onStarted(m.data);
|
_serviceMessageHandler?.onStarted(m.data);
|
||||||
case ServiceMessageType.loaded:
|
case ServiceMessageType.loaded:
|
||||||
|
|||||||
237
lib/state.dart
237
lib/state.dart
@@ -8,9 +8,7 @@ import 'package:fl_clash/plugins/service.dart';
|
|||||||
import 'package:fl_clash/plugins/vpn.dart';
|
import 'package:fl_clash/plugins/vpn.dart';
|
||||||
import 'package:fl_clash/widgets/scaffold.dart';
|
import 'package:fl_clash/widgets/scaffold.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:intl/intl.dart';
|
|
||||||
import 'package:package_info_plus/package_info_plus.dart';
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
import 'package:tray_manager/tray_manager.dart';
|
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
import 'common/common.dart';
|
import 'common/common.dart';
|
||||||
@@ -30,6 +28,8 @@ class GlobalState {
|
|||||||
late AppController appController;
|
late AppController appController;
|
||||||
GlobalKey<CommonScaffoldState> homeScaffoldKey = GlobalKey();
|
GlobalKey<CommonScaffoldState> homeScaffoldKey = GlobalKey();
|
||||||
List<Function> updateFunctionLists = [];
|
List<Function> updateFunctionLists = [];
|
||||||
|
bool lastTunEnable = false;
|
||||||
|
int? lastProfileModified;
|
||||||
|
|
||||||
bool get isStart => startTime != null && startTime!.isBeforeNow;
|
bool get isStart => startTime != null && startTime!.isBeforeNow;
|
||||||
|
|
||||||
@@ -47,16 +47,61 @@ class GlobalState {
|
|||||||
timer?.cancel();
|
timer?.cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> initCore({
|
||||||
|
required AppState appState,
|
||||||
|
required ClashConfig clashConfig,
|
||||||
|
required Config config,
|
||||||
|
}) async {
|
||||||
|
await globalState.init(
|
||||||
|
appState: appState,
|
||||||
|
config: config,
|
||||||
|
clashConfig: clashConfig,
|
||||||
|
);
|
||||||
|
await applyProfile(
|
||||||
|
appState: appState,
|
||||||
|
config: config,
|
||||||
|
clashConfig: clashConfig,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> updateClashConfig({
|
Future<void> updateClashConfig({
|
||||||
|
required AppState appState,
|
||||||
required ClashConfig clashConfig,
|
required ClashConfig clashConfig,
|
||||||
required Config config,
|
required Config config,
|
||||||
bool isPatch = true,
|
bool isPatch = true,
|
||||||
}) async {
|
}) async {
|
||||||
await config.currentProfile?.checkAndUpdate();
|
await config.currentProfile?.checkAndUpdate();
|
||||||
|
final useClashConfig = clashConfig.copyWith();
|
||||||
|
if (clashConfig.tun.enable != lastTunEnable &&
|
||||||
|
lastTunEnable == false &&
|
||||||
|
!Platform.isAndroid) {
|
||||||
|
final code = await system.authorizeCore();
|
||||||
|
switch (code) {
|
||||||
|
case AuthorizeCode.none:
|
||||||
|
break;
|
||||||
|
case AuthorizeCode.success:
|
||||||
|
lastTunEnable = useClashConfig.tun.enable;
|
||||||
|
await restartCore(
|
||||||
|
appState: appState,
|
||||||
|
clashConfig: clashConfig,
|
||||||
|
config: config,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
case AuthorizeCode.error:
|
||||||
|
useClashConfig.tun = useClashConfig.tun.copyWith(
|
||||||
|
enable: false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (config.appSetting.openLogs) {
|
||||||
|
clashCore.startLog();
|
||||||
|
} else {
|
||||||
|
clashCore.stopLog();
|
||||||
|
}
|
||||||
final res = await clashCore.updateConfig(
|
final res = await clashCore.updateConfig(
|
||||||
UpdateConfigParams(
|
UpdateConfigParams(
|
||||||
profileId: config.currentProfileId ?? "",
|
profileId: config.currentProfileId ?? "",
|
||||||
config: clashConfig,
|
config: useClashConfig,
|
||||||
params: ConfigExtendedParams(
|
params: ConfigExtendedParams(
|
||||||
isPatch: isPatch,
|
isPatch: isPatch,
|
||||||
isCompatible: true,
|
isCompatible: true,
|
||||||
@@ -67,14 +112,12 @@ class GlobalState {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
if (res.isNotEmpty) throw res;
|
if (res.isNotEmpty) throw res;
|
||||||
}
|
lastTunEnable = useClashConfig.tun.enable;
|
||||||
|
lastProfileModified = await config.getCurrentProfile()?.profileLastModified;
|
||||||
updateCoreVersionInfo(AppState appState) {
|
|
||||||
appState.versionInfo = clashCore.getVersionInfo();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleStart() async {
|
handleStart() async {
|
||||||
clashCore.start();
|
await clashCore.startListener();
|
||||||
if (globalState.isVpnService) {
|
if (globalState.isVpnService) {
|
||||||
await vpn?.startVpn();
|
await vpn?.startVpn();
|
||||||
startListenUpdate();
|
startListenUpdate();
|
||||||
@@ -85,17 +128,32 @@ class GlobalState {
|
|||||||
startListenUpdate();
|
startListenUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
restartCore({
|
||||||
|
required AppState appState,
|
||||||
|
required ClashConfig clashConfig,
|
||||||
|
required Config config,
|
||||||
|
bool isPatch = true,
|
||||||
|
}) async {
|
||||||
|
await clashService?.startCore();
|
||||||
|
await initCore(
|
||||||
|
appState: appState,
|
||||||
|
clashConfig: clashConfig,
|
||||||
|
config: config,
|
||||||
|
);
|
||||||
|
if (isStart) {
|
||||||
|
await handleStart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
updateStartTime() {
|
updateStartTime() {
|
||||||
startTime = clashCore.getRunTime();
|
startTime = clashLib?.getRunTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future handleStop() async {
|
Future handleStop() async {
|
||||||
clashCore.stop();
|
|
||||||
if (Platform.isAndroid) {
|
|
||||||
clashCore.stopTun();
|
|
||||||
}
|
|
||||||
await service?.destroy();
|
|
||||||
startTime = null;
|
startTime = null;
|
||||||
|
await clashCore.stopListener();
|
||||||
|
clashLib?.stopTun();
|
||||||
|
await service?.destroy();
|
||||||
stopListenUpdate();
|
stopListenUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,6 +164,7 @@ class GlobalState {
|
|||||||
}) async {
|
}) async {
|
||||||
clashCore.requestGc();
|
clashCore.requestGc();
|
||||||
await updateClashConfig(
|
await updateClashConfig(
|
||||||
|
appState: appState,
|
||||||
clashConfig: clashConfig,
|
clashConfig: clashConfig,
|
||||||
config: config,
|
config: config,
|
||||||
isPatch: false,
|
isPatch: false,
|
||||||
@@ -123,14 +182,13 @@ class GlobalState {
|
|||||||
required Config config,
|
required Config config,
|
||||||
required ClashConfig clashConfig,
|
required ClashConfig clashConfig,
|
||||||
}) async {
|
}) async {
|
||||||
appState.isInit = clashCore.isInit;
|
appState.isInit = await clashCore.isInit;
|
||||||
if (!appState.isInit) {
|
if (!appState.isInit) {
|
||||||
appState.isInit = await clashService.init(
|
appState.isInit = await clashCore.init(
|
||||||
config: config,
|
config: config,
|
||||||
clashConfig: clashConfig,
|
clashConfig: clashConfig,
|
||||||
);
|
);
|
||||||
if (Platform.isAndroid) {
|
clashLib?.setState(
|
||||||
clashCore.setState(
|
|
||||||
CoreState(
|
CoreState(
|
||||||
enable: config.vpnProps.enable,
|
enable: config.vpnProps.enable,
|
||||||
accessControl: config.isAccessControl ? config.accessControl : null,
|
accessControl: config.isAccessControl ? config.accessControl : null,
|
||||||
@@ -146,8 +204,6 @@ class GlobalState {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
updateCoreVersionInfo(appState);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> updateGroups(AppState appState) async {
|
Future<void> updateGroups(AppState appState) async {
|
||||||
appState.groups = await clashCore.getProxiesGroups();
|
appState.groups = await clashCore.getProxiesGroups();
|
||||||
@@ -198,8 +254,8 @@ class GlobalState {
|
|||||||
required Config config,
|
required Config config,
|
||||||
required String groupName,
|
required String groupName,
|
||||||
required String proxyName,
|
required String proxyName,
|
||||||
}) {
|
}) async {
|
||||||
clashCore.changeProxy(
|
await clashCore.changeProxy(
|
||||||
ChangeProxyParams(
|
ChangeProxyParams(
|
||||||
groupName: groupName,
|
groupName: groupName,
|
||||||
proxyName: proxyName,
|
proxyName: proxyName,
|
||||||
@@ -226,18 +282,21 @@ class GlobalState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateTraffic({
|
updateTraffic({
|
||||||
|
required Config config,
|
||||||
AppFlowingState? appFlowingState,
|
AppFlowingState? appFlowingState,
|
||||||
}) {
|
}) async {
|
||||||
final traffic = clashCore.getTraffic();
|
final onlyProxy = config.appSetting.onlyProxy;
|
||||||
|
final traffic = await clashCore.getTraffic(onlyProxy);
|
||||||
if (Platform.isAndroid && isVpnService == true) {
|
if (Platform.isAndroid && isVpnService == true) {
|
||||||
vpn?.startForeground(
|
vpn?.startForeground(
|
||||||
title: clashCore.getCurrentProfileName(),
|
title: clashLib?.getCurrentProfileName() ?? "",
|
||||||
content: "$traffic",
|
content: "$traffic",
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
if (appFlowingState != null) {
|
if (appFlowingState != null) {
|
||||||
appFlowingState.addTraffic(traffic);
|
appFlowingState.addTraffic(traffic);
|
||||||
appFlowingState.totalTraffic = clashCore.getTotalTraffic();
|
appFlowingState.totalTraffic =
|
||||||
|
await clashCore.getTotalTraffic(onlyProxy);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -301,132 +360,6 @@ class GlobalState {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future _updateSystemTray({
|
|
||||||
required Brightness? brightness,
|
|
||||||
bool force = false,
|
|
||||||
}) async {
|
|
||||||
if (Platform.isAndroid) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (Platform.isLinux || force) {
|
|
||||||
await trayManager.destroy();
|
|
||||||
}
|
|
||||||
await trayManager.setIcon(
|
|
||||||
other.getTrayIconPath(
|
|
||||||
brightness: brightness ??
|
|
||||||
WidgetsBinding.instance.platformDispatcher.platformBrightness,
|
|
||||||
),
|
|
||||||
isTemplate: true,
|
|
||||||
);
|
|
||||||
if (!Platform.isLinux) {
|
|
||||||
await trayManager.setToolTip(
|
|
||||||
appName,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updateTray({
|
|
||||||
required AppState appState,
|
|
||||||
required AppFlowingState appFlowingState,
|
|
||||||
required Config config,
|
|
||||||
required ClashConfig clashConfig,
|
|
||||||
bool focus = false,
|
|
||||||
}) async {
|
|
||||||
if (!Platform.isLinux) {
|
|
||||||
await _updateSystemTray(
|
|
||||||
brightness: appState.brightness,
|
|
||||||
force: focus,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
List<MenuItem> menuItems = [];
|
|
||||||
final showMenuItem = MenuItem(
|
|
||||||
label: appLocalizations.show,
|
|
||||||
onClick: (_) {
|
|
||||||
window?.show();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
menuItems.add(showMenuItem);
|
|
||||||
final startMenuItem = MenuItem.checkbox(
|
|
||||||
label: appFlowingState.isStart
|
|
||||||
? appLocalizations.stop
|
|
||||||
: appLocalizations.start,
|
|
||||||
onClick: (_) async {
|
|
||||||
globalState.appController.updateStart();
|
|
||||||
},
|
|
||||||
checked: false,
|
|
||||||
);
|
|
||||||
menuItems.add(startMenuItem);
|
|
||||||
menuItems.add(MenuItem.separator());
|
|
||||||
for (final mode in Mode.values) {
|
|
||||||
menuItems.add(
|
|
||||||
MenuItem.checkbox(
|
|
||||||
label: Intl.message(mode.name),
|
|
||||||
onClick: (_) {
|
|
||||||
globalState.appController.clashConfig.mode = mode;
|
|
||||||
},
|
|
||||||
checked: mode == clashConfig.mode,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
menuItems.add(MenuItem.separator());
|
|
||||||
if (appFlowingState.isStart) {
|
|
||||||
menuItems.add(
|
|
||||||
MenuItem.checkbox(
|
|
||||||
label: appLocalizations.tun,
|
|
||||||
onClick: (_) {
|
|
||||||
globalState.appController.updateTun();
|
|
||||||
},
|
|
||||||
checked: clashConfig.tun.enable,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
menuItems.add(
|
|
||||||
MenuItem.checkbox(
|
|
||||||
label: appLocalizations.systemProxy,
|
|
||||||
onClick: (_) {
|
|
||||||
globalState.appController.updateSystemProxy();
|
|
||||||
},
|
|
||||||
checked: config.networkProps.systemProxy,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
menuItems.add(MenuItem.separator());
|
|
||||||
}
|
|
||||||
final autoStartMenuItem = MenuItem.checkbox(
|
|
||||||
label: appLocalizations.autoLaunch,
|
|
||||||
onClick: (_) async {
|
|
||||||
globalState.appController.updateAutoLaunch();
|
|
||||||
},
|
|
||||||
checked: config.appSetting.autoLaunch,
|
|
||||||
);
|
|
||||||
menuItems.add(autoStartMenuItem);
|
|
||||||
|
|
||||||
if (Platform.isWindows) {
|
|
||||||
final adminAutoStartMenuItem = MenuItem.checkbox(
|
|
||||||
label: appLocalizations.adminAutoLaunch,
|
|
||||||
onClick: (_) async {
|
|
||||||
globalState.appController.updateAdminAutoLaunch();
|
|
||||||
},
|
|
||||||
checked: config.appSetting.adminAutoLaunch,
|
|
||||||
);
|
|
||||||
menuItems.add(adminAutoStartMenuItem);
|
|
||||||
}
|
|
||||||
menuItems.add(MenuItem.separator());
|
|
||||||
final exitMenuItem = MenuItem(
|
|
||||||
label: appLocalizations.exit,
|
|
||||||
onClick: (_) async {
|
|
||||||
await globalState.appController.handleExit();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
menuItems.add(exitMenuItem);
|
|
||||||
final menu = Menu(items: menuItems);
|
|
||||||
await trayManager.setContextMenu(menu);
|
|
||||||
if (Platform.isLinux) {
|
|
||||||
await _updateSystemTray(
|
|
||||||
brightness: appState.brightness,
|
|
||||||
force: focus,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final globalState = GlobalState();
|
final globalState = GlobalState();
|
||||||
|
|||||||
@@ -135,9 +135,6 @@ class CommonCard extends StatelessWidget {
|
|||||||
if (isSelected) {
|
if (isSelected) {
|
||||||
return colorScheme.secondaryContainer;
|
return colorScheme.secondaryContainer;
|
||||||
}
|
}
|
||||||
if (states.isEmpty) {
|
|
||||||
return colorScheme.surfaceContainerLow;
|
|
||||||
}
|
|
||||||
return colorScheme.surfaceContainer;
|
return colorScheme.surfaceContainer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ class CommonScaffoldState extends State<CommonScaffold> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
set floatingActionButton(Widget floatingActionButton) {
|
set floatingActionButton(Widget? floatingActionButton) {
|
||||||
if (_floatingActionButton.value != floatingActionButton) {
|
if (_floatingActionButton.value != floatingActionButton) {
|
||||||
_floatingActionButton.value = floatingActionButton;
|
_floatingActionButton.value = floatingActionButton;
|
||||||
}
|
}
|
||||||
@@ -135,12 +135,14 @@ class CommonScaffoldState extends State<CommonScaffold> {
|
|||||||
Theme.of(context).brightness == Brightness.dark
|
Theme.of(context).brightness == Brightness.dark
|
||||||
? Brightness.light
|
? Brightness.light
|
||||||
: Brightness.dark,
|
: Brightness.dark,
|
||||||
systemNavigationBarColor: widget.bottomNavigationBar != null
|
systemNavigationBarColor:
|
||||||
|
widget.bottomNavigationBar != null
|
||||||
? context.colorScheme.surfaceContainer
|
? context.colorScheme.surfaceContainer
|
||||||
: context.colorScheme.surface,
|
: context.colorScheme.surface,
|
||||||
systemNavigationBarDividerColor: Colors.transparent,
|
systemNavigationBarDividerColor: Colors.transparent,
|
||||||
),
|
),
|
||||||
automaticallyImplyLeading: widget.automaticallyImplyLeading,
|
automaticallyImplyLeading:
|
||||||
|
widget.automaticallyImplyLeading,
|
||||||
leading: widget.leading,
|
leading: widget.leading,
|
||||||
title: Text(widget.title),
|
title: Text(widget.title),
|
||||||
actions: [
|
actions: [
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
|||||||
|
|
||||||
# libclash.so
|
# libclash.so
|
||||||
set(CLASH_DIR "../libclash/linux")
|
set(CLASH_DIR "../libclash/linux")
|
||||||
install(FILES "${CLASH_DIR}/libclash.so" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
install(PROGRAMS "${CLASH_DIR}/FlClashCore" DESTINATION "${BUILD_BUNDLE_DIR}"
|
||||||
COMPONENT Runtime)
|
COMPONENT Runtime)
|
||||||
|
|
||||||
foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES})
|
foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES})
|
||||||
|
|||||||
43
macos/Podfile
Executable file
43
macos/Podfile
Executable file
@@ -0,0 +1,43 @@
|
|||||||
|
platform :osx, '10.14'
|
||||||
|
|
||||||
|
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||||
|
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||||
|
|
||||||
|
project 'Runner', {
|
||||||
|
'Debug' => :debug,
|
||||||
|
'Profile' => :release,
|
||||||
|
'Release' => :release,
|
||||||
|
}
|
||||||
|
|
||||||
|
def flutter_root
|
||||||
|
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__)
|
||||||
|
unless File.exist?(generated_xcode_build_settings_path)
|
||||||
|
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first"
|
||||||
|
end
|
||||||
|
|
||||||
|
File.foreach(generated_xcode_build_settings_path) do |line|
|
||||||
|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
|
||||||
|
return matches[1].strip if matches
|
||||||
|
end
|
||||||
|
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\""
|
||||||
|
end
|
||||||
|
|
||||||
|
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
|
||||||
|
|
||||||
|
flutter_macos_podfile_setup
|
||||||
|
|
||||||
|
target 'Runner' do
|
||||||
|
use_frameworks!
|
||||||
|
use_modular_headers!
|
||||||
|
|
||||||
|
flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__))
|
||||||
|
target 'RunnerTests' do
|
||||||
|
inherit! :search_paths
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
post_install do |installer|
|
||||||
|
installer.pods_project.targets.each do |target|
|
||||||
|
flutter_additional_macos_build_settings(target)
|
||||||
|
end
|
||||||
|
end
|
||||||
123
macos/Podfile.lock
Executable file
123
macos/Podfile.lock
Executable file
@@ -0,0 +1,123 @@
|
|||||||
|
PODS:
|
||||||
|
- app_links (1.0.0):
|
||||||
|
- FlutterMacOS
|
||||||
|
- connectivity_plus (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
- FlutterMacOS
|
||||||
|
- device_info_plus (0.0.1):
|
||||||
|
- FlutterMacOS
|
||||||
|
- dynamic_color (0.0.2):
|
||||||
|
- FlutterMacOS
|
||||||
|
- file_selector_macos (0.0.1):
|
||||||
|
- FlutterMacOS
|
||||||
|
- FlutterMacOS (1.0.0)
|
||||||
|
- HotKey (0.2.0)
|
||||||
|
- hotkey_manager_macos (0.0.1):
|
||||||
|
- FlutterMacOS
|
||||||
|
- HotKey
|
||||||
|
- mobile_scanner (6.0.2):
|
||||||
|
- FlutterMacOS
|
||||||
|
- package_info_plus (0.0.1):
|
||||||
|
- FlutterMacOS
|
||||||
|
- path_provider_foundation (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
- FlutterMacOS
|
||||||
|
- screen_retriever_macos (0.0.1):
|
||||||
|
- FlutterMacOS
|
||||||
|
- shared_preferences_foundation (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
- FlutterMacOS
|
||||||
|
- sqflite_darwin (0.0.4):
|
||||||
|
- Flutter
|
||||||
|
- FlutterMacOS
|
||||||
|
- tray_manager (0.0.1):
|
||||||
|
- FlutterMacOS
|
||||||
|
- url_launcher_macos (0.0.1):
|
||||||
|
- FlutterMacOS
|
||||||
|
- window_ext (0.0.1):
|
||||||
|
- FlutterMacOS
|
||||||
|
- window_manager (0.2.0):
|
||||||
|
- FlutterMacOS
|
||||||
|
|
||||||
|
DEPENDENCIES:
|
||||||
|
- app_links (from `Flutter/ephemeral/.symlinks/plugins/app_links/macos`)
|
||||||
|
- connectivity_plus (from `Flutter/ephemeral/.symlinks/plugins/connectivity_plus/darwin`)
|
||||||
|
- device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`)
|
||||||
|
- dynamic_color (from `Flutter/ephemeral/.symlinks/plugins/dynamic_color/macos`)
|
||||||
|
- file_selector_macos (from `Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos`)
|
||||||
|
- FlutterMacOS (from `Flutter/ephemeral`)
|
||||||
|
- hotkey_manager_macos (from `Flutter/ephemeral/.symlinks/plugins/hotkey_manager_macos/macos`)
|
||||||
|
- mobile_scanner (from `Flutter/ephemeral/.symlinks/plugins/mobile_scanner/macos`)
|
||||||
|
- package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`)
|
||||||
|
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
|
||||||
|
- screen_retriever_macos (from `Flutter/ephemeral/.symlinks/plugins/screen_retriever_macos/macos`)
|
||||||
|
- shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||||
|
- sqflite_darwin (from `Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin`)
|
||||||
|
- tray_manager (from `Flutter/ephemeral/.symlinks/plugins/tray_manager/macos`)
|
||||||
|
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
|
||||||
|
- window_ext (from `Flutter/ephemeral/.symlinks/plugins/window_ext/macos`)
|
||||||
|
- window_manager (from `Flutter/ephemeral/.symlinks/plugins/window_manager/macos`)
|
||||||
|
|
||||||
|
SPEC REPOS:
|
||||||
|
trunk:
|
||||||
|
- HotKey
|
||||||
|
|
||||||
|
EXTERNAL SOURCES:
|
||||||
|
app_links:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/app_links/macos
|
||||||
|
connectivity_plus:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/connectivity_plus/darwin
|
||||||
|
device_info_plus:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos
|
||||||
|
dynamic_color:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/dynamic_color/macos
|
||||||
|
file_selector_macos:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos
|
||||||
|
FlutterMacOS:
|
||||||
|
:path: Flutter/ephemeral
|
||||||
|
hotkey_manager_macos:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/hotkey_manager_macos/macos
|
||||||
|
mobile_scanner:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/mobile_scanner/macos
|
||||||
|
package_info_plus:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos
|
||||||
|
path_provider_foundation:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin
|
||||||
|
screen_retriever_macos:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/screen_retriever_macos/macos
|
||||||
|
shared_preferences_foundation:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin
|
||||||
|
sqflite_darwin:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin
|
||||||
|
tray_manager:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/tray_manager/macos
|
||||||
|
url_launcher_macos:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
|
||||||
|
window_ext:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/window_ext/macos
|
||||||
|
window_manager:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/window_manager/macos
|
||||||
|
|
||||||
|
SPEC CHECKSUMS:
|
||||||
|
app_links: 10e0a0ab602ffaf34d142cd4862f29d34b303b2a
|
||||||
|
connectivity_plus: 4c41c08fc6d7c91f63bc7aec70ffe3730b04f563
|
||||||
|
device_info_plus: ce1b7762849d3ec103d0e0517299f2db7ad60720
|
||||||
|
dynamic_color: 2eaa27267de1ca20d879fbd6e01259773fb1670f
|
||||||
|
file_selector_macos: cc3858c981fe6889f364731200d6232dac1d812d
|
||||||
|
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
|
||||||
|
HotKey: e96d8a2ddbf4591131e2bb3f54e69554d90cdca6
|
||||||
|
hotkey_manager_macos: 1e2edb0c7ae4fe67108af44a9d3445de41404160
|
||||||
|
mobile_scanner: 07710d6b9b2c220ae899de2d7ecf5d77ffa56333
|
||||||
|
package_info_plus: 12f1c5c2cfe8727ca46cbd0b26677728972d9a5b
|
||||||
|
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
|
||||||
|
screen_retriever_macos: 776e0fa5d42c6163d2bf772d22478df4b302b161
|
||||||
|
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
|
||||||
|
sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d
|
||||||
|
tray_manager: 9064e219c56d75c476e46b9a21182087930baf90
|
||||||
|
url_launcher_macos: c82c93949963e55b228a30115bd219499a6fe404
|
||||||
|
window_ext: cbf41f054296dcfb15a1cb53610ef3eab6311f13
|
||||||
|
window_manager: 3a1844359a6295ab1e47659b1a777e36773cd6e8
|
||||||
|
|
||||||
|
PODFILE CHECKSUM: 236401fc2c932af29a9fcf0e97baeeb2d750d367
|
||||||
|
|
||||||
|
COCOAPODS: 1.16.2
|
||||||
@@ -28,10 +28,9 @@
|
|||||||
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; };
|
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; };
|
||||||
33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; };
|
33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; };
|
||||||
5377B2253E1C5AB4D9D56A31 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 72CBDF47BB69EDEFE644C48D /* Pods_RunnerTests.framework */; };
|
5377B2253E1C5AB4D9D56A31 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 72CBDF47BB69EDEFE644C48D /* Pods_RunnerTests.framework */; };
|
||||||
7AC277AA2B90DE1400E026B1 /* libclash.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 7AC277A92B90DE1400E026B1 /* libclash.dylib */; settings = {ATTRIBUTES = (Weak, ); }; };
|
|
||||||
7AC277AB2B90DFD900E026B1 /* libclash.dylib in Bundle Framework */ = {isa = PBXBuildFile; fileRef = 7AC277A92B90DE1400E026B1 /* libclash.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
|
||||||
7AC6855B2B8AF836004C123B /* (null) in Bundle Framework */ = {isa = PBXBuildFile; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
7AC6855B2B8AF836004C123B /* (null) in Bundle Framework */ = {isa = PBXBuildFile; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
||||||
CDD319C761C7664F6008596B /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4121E8CCDC7DC35194714CDE /* Pods_Runner.framework */; };
|
CDD319C761C7664F6008596B /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4121E8CCDC7DC35194714CDE /* Pods_Runner.framework */; };
|
||||||
|
F50091052CF74B7700D43AEA /* FlClashCore in CopyFiles */ = {isa = PBXBuildFile; fileRef = F50091042CF74B7700D43AEA /* FlClashCore */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
@@ -58,12 +57,39 @@
|
|||||||
dstPath = "";
|
dstPath = "";
|
||||||
dstSubfolderSpec = 10;
|
dstSubfolderSpec = 10;
|
||||||
files = (
|
files = (
|
||||||
7AC277AB2B90DFD900E026B1 /* libclash.dylib in Bundle Framework */,
|
|
||||||
7AC6855B2B8AF836004C123B /* (null) in Bundle Framework */,
|
7AC6855B2B8AF836004C123B /* (null) in Bundle Framework */,
|
||||||
);
|
);
|
||||||
name = "Bundle Framework";
|
name = "Bundle Framework";
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
F50091032CF74B6400D43AEA /* CopyFiles */ = {
|
||||||
|
isa = PBXCopyFilesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
dstPath = "";
|
||||||
|
dstSubfolderSpec = 6;
|
||||||
|
files = (
|
||||||
|
F50091052CF74B7700D43AEA /* FlClashCore in CopyFiles */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
F5FAC0AA2CEDC4DA000CF079 /* CopyFiles */ = {
|
||||||
|
isa = PBXCopyFilesBuildPhase;
|
||||||
|
buildActionMask = 12;
|
||||||
|
dstPath = "";
|
||||||
|
dstSubfolderSpec = 6;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
F5FAC0AE2CEDC891000CF079 /* CopyFiles */ = {
|
||||||
|
isa = PBXCopyFilesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
dstPath = "";
|
||||||
|
dstSubfolderSpec = 6;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
/* End PBXCopyFilesBuildPhase section */
|
/* End PBXCopyFilesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
@@ -87,13 +113,13 @@
|
|||||||
4121E8CCDC7DC35194714CDE /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
4121E8CCDC7DC35194714CDE /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
72CBDF47BB69EDEFE644C48D /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
72CBDF47BB69EDEFE644C48D /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
779829C96DE7998FCC810C37 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
|
779829C96DE7998FCC810C37 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
|
||||||
7AC277A92B90DE1400E026B1 /* libclash.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libclash.dylib; path = ../libclash/macos/libclash.dylib; sourceTree = "<group>"; };
|
|
||||||
7AF070893C29500AB9129D89 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
|
7AF070893C29500AB9129D89 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
|
||||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; };
|
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; };
|
||||||
7D929F2AFD80E155D78F3718 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
|
7D929F2AFD80E155D78F3718 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
|
||||||
8F1D6D6423063FA738863205 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
|
8F1D6D6423063FA738863205 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
|
||||||
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
|
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
|
||||||
CA9CA9C2D0B5E93A91F45924 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
|
CA9CA9C2D0B5E93A91F45924 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
F50091042CF74B7700D43AEA /* FlClashCore */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; name = FlClashCore; path = ../libclash/macos/FlClashCore; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
@@ -109,7 +135,6 @@
|
|||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
7AC277AA2B90DE1400E026B1 /* libclash.dylib in Frameworks */,
|
|
||||||
CDD319C761C7664F6008596B /* Pods_Runner.framework in Frameworks */,
|
CDD319C761C7664F6008596B /* Pods_Runner.framework in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
@@ -139,6 +164,7 @@
|
|||||||
33CC10E42044A3C60003C045 = {
|
33CC10E42044A3C60003C045 = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
F50091042CF74B7700D43AEA /* FlClashCore */,
|
||||||
33FAB671232836740065AC1E /* Runner */,
|
33FAB671232836740065AC1E /* Runner */,
|
||||||
33CEB47122A05771004F2AC0 /* Flutter */,
|
33CEB47122A05771004F2AC0 /* Flutter */,
|
||||||
331C80D6294CF71000263BE5 /* RunnerTests */,
|
331C80D6294CF71000263BE5 /* RunnerTests */,
|
||||||
@@ -208,7 +234,6 @@
|
|||||||
D73912EC22F37F3D000D13A0 /* Frameworks */ = {
|
D73912EC22F37F3D000D13A0 /* Frameworks */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
7AC277A92B90DE1400E026B1 /* libclash.dylib */,
|
|
||||||
4121E8CCDC7DC35194714CDE /* Pods_Runner.framework */,
|
4121E8CCDC7DC35194714CDE /* Pods_Runner.framework */,
|
||||||
72CBDF47BB69EDEFE644C48D /* Pods_RunnerTests.framework */,
|
72CBDF47BB69EDEFE644C48D /* Pods_RunnerTests.framework */,
|
||||||
);
|
);
|
||||||
@@ -226,6 +251,7 @@
|
|||||||
331C80D1294CF70F00263BE5 /* Sources */,
|
331C80D1294CF70F00263BE5 /* Sources */,
|
||||||
331C80D2294CF70F00263BE5 /* Frameworks */,
|
331C80D2294CF70F00263BE5 /* Frameworks */,
|
||||||
331C80D3294CF70F00263BE5 /* Resources */,
|
331C80D3294CF70F00263BE5 /* Resources */,
|
||||||
|
F5FAC0AA2CEDC4DA000CF079 /* CopyFiles */,
|
||||||
);
|
);
|
||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
@@ -248,6 +274,8 @@
|
|||||||
33CC110E2044A8840003C045 /* Bundle Framework */,
|
33CC110E2044A8840003C045 /* Bundle Framework */,
|
||||||
3399D490228B24CF009A79C7 /* ShellScript */,
|
3399D490228B24CF009A79C7 /* ShellScript */,
|
||||||
1522C6AC211009D2A7DFAD40 /* [CP] Embed Pods Frameworks */,
|
1522C6AC211009D2A7DFAD40 /* [CP] Embed Pods Frameworks */,
|
||||||
|
F5FAC0AE2CEDC891000CF079 /* CopyFiles */,
|
||||||
|
F50091032CF74B6400D43AEA /* CopyFiles */,
|
||||||
);
|
);
|
||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
@@ -582,7 +610,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/../Frameworks",
|
"@executable_path/../Frameworks",
|
||||||
);
|
);
|
||||||
LIBRARY_SEARCH_PATHS = "${SRCROOT}/../libclash/macos/";
|
LIBRARY_SEARCH_PATHS = "";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.follow.clash;
|
PRODUCT_BUNDLE_IDENTIFIER = com.follow.clash;
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
@@ -710,7 +738,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/../Frameworks",
|
"@executable_path/../Frameworks",
|
||||||
);
|
);
|
||||||
LIBRARY_SEARCH_PATHS = "${SRCROOT}/../libclash/macos/";
|
LIBRARY_SEARCH_PATHS = "";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.follow.clash;
|
PRODUCT_BUNDLE_IDENTIFIER = com.follow.clash;
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
@@ -732,7 +760,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/../Frameworks",
|
"@executable_path/../Frameworks",
|
||||||
);
|
);
|
||||||
LIBRARY_SEARCH_PATHS = "${SRCROOT}/../libclash/macos/";
|
LIBRARY_SEARCH_PATHS = "";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.follow.clash;
|
PRODUCT_BUNDLE_IDENTIFIER = com.follow.clash;
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
name: fl_clash
|
name: fl_clash
|
||||||
description: A multi-platform proxy client based on ClashMeta, simple and easy to use, open-source and ad-free.
|
description: A multi-platform proxy client based on ClashMeta, simple and easy to use, open-source and ad-free.
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
version: 0.8.68+202411171
|
version: 0.8.69+202412061
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.1.0 <4.0.0'
|
sdk: '>=3.1.0 <4.0.0'
|
||||||
|
|
||||||
|
|||||||
1313
services/helper/Cargo.lock
generated
Normal file
1313
services/helper/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
23
services/helper/Cargo.toml
Normal file
23
services/helper/Cargo.toml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
[package]
|
||||||
|
name = "helper"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "helper"
|
||||||
|
path = "src/main.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
windows-service = { version = "0.7.0", optional = true }
|
||||||
|
tokio = { version = "1", features = ["full"] }
|
||||||
|
anyhow = "1.0.93"
|
||||||
|
warp = "0.3.7"
|
||||||
|
serde = { version = "1.0.215", features = ["derive"] }
|
||||||
|
once_cell = "1.20.2"
|
||||||
|
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
panic = "abort"
|
||||||
|
codegen-units = 1
|
||||||
|
lto = true
|
||||||
|
opt-level = "s"
|
||||||
20
services/helper/src/main.rs
Normal file
20
services/helper/src/main.rs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
#[cfg(not(all(feature = "windows-service", target_os = "windows")))]
|
||||||
|
use tokio::runtime::Runtime;
|
||||||
|
#[cfg(not(all(feature = "windows-service", target_os = "windows")))]
|
||||||
|
use crate::service::hub::run_service;
|
||||||
|
|
||||||
|
mod service;
|
||||||
|
|
||||||
|
#[cfg(all(feature = "windows-service", target_os = "windows"))]
|
||||||
|
pub fn main() -> windows_service::Result<()> {
|
||||||
|
service::windows::main()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(all(feature = "windows-service", target_os = "windows")))]
|
||||||
|
fn main() {
|
||||||
|
if let Ok(rt) = Runtime::new() {
|
||||||
|
rt.block_on(async {
|
||||||
|
let _ = run_service().await;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
110
services/helper/src/service/hub.rs
Normal file
110
services/helper/src/service/hub.rs
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
use std::collections::VecDeque;
|
||||||
|
use std::{io, thread};
|
||||||
|
use std::io::BufRead;
|
||||||
|
use std::process::{Command, Stdio};
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
use warp::{Filter, Reply};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
|
const LISTEN_PORT: u16 = 47890;
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||||
|
pub struct StartParams {
|
||||||
|
pub path: String,
|
||||||
|
pub arg: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
static LOGS: Lazy<Arc<Mutex<VecDeque<String>>>> = Lazy::new(|| Arc::new(Mutex::new(VecDeque::with_capacity(100))));
|
||||||
|
static PROCESS: Lazy<Arc<Mutex<Option<std::process::Child>>>> = Lazy::new(|| Arc::new(Mutex::new(None)));
|
||||||
|
|
||||||
|
fn start(start_params: StartParams) -> impl Reply {
|
||||||
|
stop();
|
||||||
|
let mut process = PROCESS.lock().unwrap();
|
||||||
|
match Command::new(&start_params.path)
|
||||||
|
.stderr(Stdio::piped())
|
||||||
|
.arg(&start_params.arg)
|
||||||
|
.spawn()
|
||||||
|
{
|
||||||
|
Ok(child) => {
|
||||||
|
*process = Some(child);
|
||||||
|
if let Some(ref mut child) = *process {
|
||||||
|
let stderr = child.stderr.take().unwrap();
|
||||||
|
let reader = io::BufReader::new(stderr);
|
||||||
|
thread::spawn(move || {
|
||||||
|
for line in reader.lines() {
|
||||||
|
match line {
|
||||||
|
Ok(output) => {
|
||||||
|
log_message(output);
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
"".to_string()
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log_message(e.to_string());
|
||||||
|
e.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stop() -> impl Reply {
|
||||||
|
let mut process = PROCESS.lock().unwrap();
|
||||||
|
if let Some(mut child) = process.take() {
|
||||||
|
let _ = child.kill();
|
||||||
|
let _ = child.wait();
|
||||||
|
}
|
||||||
|
*process = None;
|
||||||
|
"".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn log_message(message: String) {
|
||||||
|
let mut log_buffer = LOGS.lock().unwrap();
|
||||||
|
if log_buffer.len() == 100 {
|
||||||
|
log_buffer.pop_front();
|
||||||
|
}
|
||||||
|
log_buffer.push_back(format!("{}\n", message));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_logs() -> impl Reply {
|
||||||
|
let log_buffer = LOGS.lock().unwrap();
|
||||||
|
let value = log_buffer.iter().cloned().collect::<Vec<String>>().join("\n");
|
||||||
|
warp::reply::with_header(value, "Content-Type", "text/plain")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn run_service() -> anyhow::Result<()> {
|
||||||
|
let api_ping = warp::get()
|
||||||
|
.and(warp::path("ping"))
|
||||||
|
.map(|| "2024125");
|
||||||
|
|
||||||
|
let api_start = warp::post()
|
||||||
|
.and(warp::path("start"))
|
||||||
|
.and(warp::body::json())
|
||||||
|
.map(|start_params: StartParams| {
|
||||||
|
start(start_params)
|
||||||
|
});
|
||||||
|
|
||||||
|
let api_stop = warp::post()
|
||||||
|
.and(warp::path("stop"))
|
||||||
|
.map(|| stop());
|
||||||
|
|
||||||
|
let api_logs = warp::get()
|
||||||
|
.and(warp::path("logs"))
|
||||||
|
.map(|| get_logs());
|
||||||
|
|
||||||
|
warp::serve(
|
||||||
|
api_ping
|
||||||
|
.or(api_start)
|
||||||
|
.or(api_stop)
|
||||||
|
.or(api_logs)
|
||||||
|
)
|
||||||
|
.run(([127, 0, 0, 1], LISTEN_PORT))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
8
services/helper/src/service/mod.rs
Normal file
8
services/helper/src/service/mod.rs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
pub mod hub;
|
||||||
|
#[cfg(all(feature = "windows-service", target_os = "windows"))]
|
||||||
|
pub mod windows;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
67
services/helper/src/service/windows.rs
Normal file
67
services/helper/src/service/windows.rs
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
use crate::service::hub::run_service;
|
||||||
|
|
||||||
|
use std::ffi::OsString;
|
||||||
|
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use tokio::runtime::Runtime;
|
||||||
|
|
||||||
|
use windows_service::{
|
||||||
|
define_windows_service,
|
||||||
|
service::{
|
||||||
|
ServiceControl, ServiceControlAccept, ServiceExitCode, ServiceState, ServiceStatus,
|
||||||
|
ServiceType,
|
||||||
|
},
|
||||||
|
service_control_handler::{self, ServiceControlHandlerResult},
|
||||||
|
service_dispatcher, Result,
|
||||||
|
};
|
||||||
|
|
||||||
|
const SERVICE_NAME: &str = "FlClashHelperService";
|
||||||
|
|
||||||
|
const SERVICE_TYPE: ServiceType = ServiceType::OWN_PROCESS;
|
||||||
|
|
||||||
|
pub fn main() -> Result<()> {
|
||||||
|
start_service()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn start_service() -> Result<()> {
|
||||||
|
service_dispatcher::start(SERVICE_NAME, serveice)
|
||||||
|
}
|
||||||
|
|
||||||
|
define_windows_service!(serveice, service_main);
|
||||||
|
|
||||||
|
pub fn service_main(_arguments: Vec<OsString>) {
|
||||||
|
if let Ok(rt) = Runtime::new() {
|
||||||
|
rt.block_on(async {
|
||||||
|
let _ = run_windows_service().await;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async fn run_windows_service() -> anyhow::Result<()> {
|
||||||
|
let status_handle = service_control_handler::register(
|
||||||
|
SERVICE_NAME,
|
||||||
|
move |event| -> ServiceControlHandlerResult {
|
||||||
|
match event {
|
||||||
|
ServiceControl::Interrogate => ServiceControlHandlerResult::NoError,
|
||||||
|
ServiceControl::Stop => std::process::exit(0),
|
||||||
|
_ => ServiceControlHandlerResult::NotImplemented,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
status_handle.set_service_status(ServiceStatus {
|
||||||
|
service_type: SERVICE_TYPE,
|
||||||
|
current_state: ServiceState::Running,
|
||||||
|
controls_accepted: ServiceControlAccept::STOP,
|
||||||
|
exit_code: ServiceExitCode::Win32(0),
|
||||||
|
checkpoint: 0,
|
||||||
|
wait_hint: Duration::default(),
|
||||||
|
process_id: None,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
run_service().await
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
298
setup.dart
298
setup.dart
@@ -2,115 +2,121 @@
|
|||||||
|
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:args/command_runner.dart';
|
import 'package:args/command_runner.dart';
|
||||||
import 'package:path/path.dart';
|
import 'package:path/path.dart';
|
||||||
|
|
||||||
enum PlatformType {
|
enum Target {
|
||||||
windows,
|
windows,
|
||||||
linux,
|
linux,
|
||||||
android,
|
android,
|
||||||
macos,
|
macos,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Arch { amd64, arm64, arm }
|
extension TargetExt on Target {
|
||||||
|
String get os {
|
||||||
class BuildLibItem {
|
if (this == Target.macos) {
|
||||||
PlatformType platform;
|
return "darwin";
|
||||||
Arch arch;
|
}
|
||||||
String archName;
|
return name;
|
||||||
|
}
|
||||||
BuildLibItem({
|
|
||||||
required this.platform,
|
|
||||||
required this.arch,
|
|
||||||
required this.archName,
|
|
||||||
});
|
|
||||||
|
|
||||||
String get dynamicLibExtensionName {
|
String get dynamicLibExtensionName {
|
||||||
final String extensionName;
|
final String extensionName;
|
||||||
switch (platform) {
|
switch (this) {
|
||||||
case PlatformType.android || PlatformType.linux:
|
case Target.android || Target.linux:
|
||||||
extensionName = "so";
|
extensionName = ".so";
|
||||||
break;
|
break;
|
||||||
case PlatformType.windows:
|
case Target.windows:
|
||||||
extensionName = "dll";
|
extensionName = ".dll";
|
||||||
break;
|
break;
|
||||||
case PlatformType.macos:
|
case Target.macos:
|
||||||
extensionName = "dylib";
|
extensionName = ".dylib";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return extensionName;
|
return extensionName;
|
||||||
}
|
}
|
||||||
|
|
||||||
String get os {
|
String get executableExtensionName {
|
||||||
if (platform == PlatformType.macos) {
|
final String extensionName;
|
||||||
return "darwin";
|
switch (this) {
|
||||||
|
case Target.windows:
|
||||||
|
extensionName = ".exe";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
extensionName = "";
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
return platform.name;
|
return extensionName;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Mode { core, lib }
|
||||||
|
|
||||||
|
enum Arch { amd64, arm64, arm }
|
||||||
|
|
||||||
|
class BuildItem {
|
||||||
|
Target target;
|
||||||
|
Arch? arch;
|
||||||
|
String? archName;
|
||||||
|
|
||||||
|
BuildItem({
|
||||||
|
required this.target,
|
||||||
|
this.arch,
|
||||||
|
this.archName,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'BuildLibItem{platform: $platform, arch: $arch, archName: $archName}';
|
return 'BuildLibItem{target: $target, arch: $arch, archName: $archName}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Build {
|
class Build {
|
||||||
static List<BuildLibItem> get buildItems => [
|
static List<BuildItem> get buildItems => [
|
||||||
BuildLibItem(
|
BuildItem(
|
||||||
platform: PlatformType.macos,
|
target: Target.macos,
|
||||||
arch: Arch.amd64,
|
|
||||||
archName: '',
|
|
||||||
),
|
),
|
||||||
BuildLibItem(
|
BuildItem(
|
||||||
platform: PlatformType.macos,
|
target: Target.windows,
|
||||||
arch: Arch.arm64,
|
|
||||||
archName: '',
|
|
||||||
),
|
),
|
||||||
BuildLibItem(
|
BuildItem(
|
||||||
platform: PlatformType.windows,
|
target: Target.linux,
|
||||||
arch: Arch.amd64,
|
|
||||||
archName: '',
|
|
||||||
),
|
),
|
||||||
BuildLibItem(
|
BuildItem(
|
||||||
platform: PlatformType.windows,
|
target: Target.android,
|
||||||
arch: Arch.arm64,
|
|
||||||
archName: '',
|
|
||||||
),
|
|
||||||
BuildLibItem(
|
|
||||||
platform: PlatformType.android,
|
|
||||||
arch: Arch.arm,
|
arch: Arch.arm,
|
||||||
archName: 'armeabi-v7a',
|
archName: 'armeabi-v7a',
|
||||||
),
|
),
|
||||||
BuildLibItem(
|
BuildItem(
|
||||||
platform: PlatformType.android,
|
target: Target.android,
|
||||||
arch: Arch.arm64,
|
arch: Arch.arm64,
|
||||||
archName: 'arm64-v8a',
|
archName: 'arm64-v8a',
|
||||||
),
|
),
|
||||||
BuildLibItem(
|
BuildItem(
|
||||||
platform: PlatformType.android,
|
target: Target.android,
|
||||||
arch: Arch.amd64,
|
arch: Arch.amd64,
|
||||||
archName: 'x86_64',
|
archName: 'x86_64',
|
||||||
),
|
),
|
||||||
BuildLibItem(
|
|
||||||
platform: PlatformType.linux,
|
|
||||||
arch: Arch.amd64,
|
|
||||||
archName: '',
|
|
||||||
),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
static String get appName => "FlClash";
|
static String get appName => "FlClash";
|
||||||
|
|
||||||
|
static String get coreName => "FlClashCore";
|
||||||
|
|
||||||
static String get libName => "libclash";
|
static String get libName => "libclash";
|
||||||
|
|
||||||
static String get outDir => join(current, libName);
|
static String get outDir => join(current, libName);
|
||||||
|
|
||||||
static String get _coreDir => join(current, "core");
|
static String get _coreDir => join(current, "core");
|
||||||
|
|
||||||
|
static String get _servicesDir => join(current, "services", "helper");
|
||||||
|
|
||||||
static String get distPath => join(current, "dist");
|
static String get distPath => join(current, "dist");
|
||||||
|
|
||||||
static String _getCc(BuildLibItem buildItem) {
|
static String _getCc(BuildItem buildItem) {
|
||||||
final environment = Platform.environment;
|
final environment = Platform.environment;
|
||||||
if (buildItem.platform == PlatformType.android) {
|
if (buildItem.target == Target.android) {
|
||||||
final ndk = environment["ANDROID_NDK"];
|
final ndk = environment["ANDROID_NDK"];
|
||||||
assert(ndk != null);
|
assert(ndk != null);
|
||||||
final prebuiltDir =
|
final prebuiltDir =
|
||||||
@@ -158,55 +164,97 @@ class Build {
|
|||||||
if (exitCode != 0 && name != null) throw "$name error";
|
if (exitCode != 0 && name != null) throw "$name error";
|
||||||
}
|
}
|
||||||
|
|
||||||
static buildLib({
|
static buildCore({
|
||||||
required PlatformType platform,
|
required Mode mode,
|
||||||
|
required Target target,
|
||||||
Arch? arch,
|
Arch? arch,
|
||||||
}) async {
|
}) async {
|
||||||
|
final isLib = mode == Mode.lib;
|
||||||
|
|
||||||
final items = buildItems.where(
|
final items = buildItems.where(
|
||||||
(element) {
|
(element) {
|
||||||
return element.platform == platform &&
|
return element.target == target &&
|
||||||
(arch == null ? true : element.arch == arch);
|
(arch == null ? true : element.arch == arch);
|
||||||
},
|
},
|
||||||
).toList();
|
).toList();
|
||||||
|
|
||||||
for (final item in items) {
|
for (final item in items) {
|
||||||
final outFileDir = join(
|
final outFileDir = join(
|
||||||
outDir,
|
outDir,
|
||||||
item.platform.name,
|
item.target.name,
|
||||||
item.archName,
|
item.archName,
|
||||||
);
|
);
|
||||||
|
|
||||||
final file = File(outFileDir);
|
final file = File(outFileDir);
|
||||||
if (file.existsSync()) {
|
if (file.existsSync()) {
|
||||||
file.deleteSync(recursive: true);
|
file.deleteSync(recursive: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final fileName = isLib
|
||||||
|
? "$libName${item.target.dynamicLibExtensionName}"
|
||||||
|
: "$coreName${item.target.executableExtensionName}";
|
||||||
final outPath = join(
|
final outPath = join(
|
||||||
outFileDir,
|
outFileDir,
|
||||||
"$libName.${item.dynamicLibExtensionName}",
|
fileName,
|
||||||
);
|
);
|
||||||
|
|
||||||
final Map<String, String> env = {};
|
final Map<String, String> env = {};
|
||||||
env["GOOS"] = item.os;
|
env["GOOS"] = item.target.os;
|
||||||
env["GOARCH"] = item.arch.name;
|
|
||||||
|
if (isLib) {
|
||||||
|
if (item.arch != null) {
|
||||||
|
env["GOARCH"] = item.arch!.name;
|
||||||
|
}
|
||||||
env["CGO_ENABLED"] = "1";
|
env["CGO_ENABLED"] = "1";
|
||||||
env["CC"] = _getCc(item);
|
env["CC"] = _getCc(item);
|
||||||
env["CFLAGS"] = "-O3 -Werror";
|
env["CFLAGS"] = "-O3 -Werror";
|
||||||
|
} else {
|
||||||
|
env["CGO_ENABLED"] = "0";
|
||||||
|
}
|
||||||
|
|
||||||
await exec(
|
final execLines = [
|
||||||
[
|
|
||||||
"go",
|
"go",
|
||||||
"build",
|
"build",
|
||||||
"-ldflags=-w -s",
|
"-ldflags=-w -s",
|
||||||
"-tags=$tags",
|
"-tags=$tags",
|
||||||
"-buildmode=c-shared",
|
if (isLib) "-buildmode=c-shared",
|
||||||
"-o",
|
"-o",
|
||||||
outPath,
|
outPath,
|
||||||
],
|
];
|
||||||
name: "build libclash",
|
await exec(
|
||||||
|
execLines,
|
||||||
|
name: "build core",
|
||||||
environment: env,
|
environment: env,
|
||||||
workingDirectory: _coreDir,
|
workingDirectory: _coreDir,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static buildHelper(Target target) async {
|
||||||
|
await exec(
|
||||||
|
[
|
||||||
|
"cargo",
|
||||||
|
"build",
|
||||||
|
"--release",
|
||||||
|
"--features",
|
||||||
|
"windows-service",
|
||||||
|
],
|
||||||
|
name: "build helper",
|
||||||
|
workingDirectory: _servicesDir,
|
||||||
|
);
|
||||||
|
final outPath = join(
|
||||||
|
_servicesDir,
|
||||||
|
"target",
|
||||||
|
"release",
|
||||||
|
"helper${target.executableExtensionName}",
|
||||||
|
);
|
||||||
|
final targetPath = join(outDir, target.name,
|
||||||
|
"FlClashHelperService${target.executableExtensionName}");
|
||||||
|
await File(outPath).copy(targetPath);
|
||||||
|
}
|
||||||
|
|
||||||
static List<String> getExecutable(String command) {
|
static List<String> getExecutable(String command) {
|
||||||
|
print(command);
|
||||||
return command.split(" ");
|
return command.split(" ");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -250,22 +298,30 @@ class Build {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class BuildCommand extends Command {
|
class BuildCommand extends Command {
|
||||||
PlatformType platform;
|
Target target;
|
||||||
|
|
||||||
BuildCommand({
|
BuildCommand({
|
||||||
required this.platform,
|
required this.target,
|
||||||
}) {
|
}) {
|
||||||
argParser.addOption(
|
if (target == Target.android) {
|
||||||
"build",
|
|
||||||
valueHelp: [
|
|
||||||
'all',
|
|
||||||
'lib',
|
|
||||||
].join(','),
|
|
||||||
help: 'The $name build type',
|
|
||||||
);
|
|
||||||
argParser.addOption(
|
argParser.addOption(
|
||||||
"arch",
|
"arch",
|
||||||
valueHelp: arches.map((e) => e.name).join(','),
|
valueHelp: arches.map((e) => e.name).join(','),
|
||||||
|
help: 'The $name build desc',
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
argParser.addOption(
|
||||||
|
"arch",
|
||||||
|
help: 'The $name build archName',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
argParser.addOption(
|
||||||
|
"out",
|
||||||
|
valueHelp: [
|
||||||
|
"app",
|
||||||
|
"core",
|
||||||
|
].join(','),
|
||||||
help: 'The $name build arch',
|
help: 'The $name build arch',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -274,17 +330,13 @@ class BuildCommand extends Command {
|
|||||||
String get description => "build $name application";
|
String get description => "build $name application";
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get name => platform.name;
|
String get name => target.name;
|
||||||
|
|
||||||
List<Arch> get arches => Build.buildItems
|
List<Arch> get arches => Build.buildItems
|
||||||
.where((element) => element.platform == platform)
|
.where((element) => element.target == target && element.arch != null)
|
||||||
.map((e) => e.arch)
|
.map((e) => e.arch!)
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
Future<void> _buildLib(Arch? arch) async {
|
|
||||||
await Build.buildLib(platform: platform, arch: arch);
|
|
||||||
}
|
|
||||||
|
|
||||||
_getLinuxDependencies() async {
|
_getLinuxDependencies() async {
|
||||||
await Build.exec(
|
await Build.exec(
|
||||||
Build.getExecutable("sudo apt update -y"),
|
Build.getExecutable("sudo apt update -y"),
|
||||||
@@ -331,51 +383,71 @@ class BuildCommand extends Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_buildDistributor({
|
_buildDistributor({
|
||||||
required PlatformType platform,
|
required Target target,
|
||||||
required String targets,
|
required String targets,
|
||||||
String args = '',
|
String args = '',
|
||||||
}) async {
|
}) async {
|
||||||
await Build.getDistributor();
|
await Build.getDistributor();
|
||||||
/* final tag = Platform.environment["TAG"] ?? "+";
|
|
||||||
final isDev = tag.contains("+");
|
|
||||||
final channelArgs = isDev && platform == PlatformType.android ? "--build-flavor dev" : "";*/
|
|
||||||
await Build.exec(
|
await Build.exec(
|
||||||
name: name,
|
name: name,
|
||||||
Build.getExecutable(
|
Build.getExecutable(
|
||||||
"flutter_distributor package --skip-clean --platform ${platform.name} --targets $targets --flutter-build-args=verbose $args",
|
"flutter_distributor package --skip-clean --platform ${target.name} --targets $targets --flutter-build-args=verbose $args",
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<String?> get systemArch async {
|
||||||
|
if (Platform.isWindows) {
|
||||||
|
return Platform.environment["PROCESSOR_ARCHITECTURE"];
|
||||||
|
} else if (Platform.isLinux || Platform.isMacOS) {
|
||||||
|
final result = await Process.run('uname', ['-m']);
|
||||||
|
return result.stdout.toString().trim();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> run() async {
|
Future<void> run() async {
|
||||||
final String build = argResults?['build'] ?? 'all';
|
final mode = target == Target.android ? Mode.lib : Mode.core;
|
||||||
final archName = argResults?['arch'];
|
final String out = argResults?['out'] ?? 'app';
|
||||||
|
Arch? arch;
|
||||||
|
var archName = argResults?['arch'];
|
||||||
|
|
||||||
|
if (target == Target.android) {
|
||||||
final currentArches =
|
final currentArches =
|
||||||
arches.where((element) => element.name == archName).toList();
|
arches.where((element) => element.name == archName).toList();
|
||||||
final arch = currentArches.isEmpty ? null : currentArches.first;
|
arch = currentArches.isEmpty ? null : currentArches.first;
|
||||||
if (arch == null && platform == PlatformType.windows) {
|
|
||||||
throw "Invalid arch";
|
|
||||||
}
|
}
|
||||||
await _buildLib(arch);
|
|
||||||
if (build != "all") {
|
await Build.buildCore(
|
||||||
|
target: target,
|
||||||
|
arch: arch,
|
||||||
|
mode: mode,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (target == Target.windows) {
|
||||||
|
await Build.buildHelper(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (out != "app") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
switch (platform) {
|
|
||||||
case PlatformType.windows:
|
switch (target) {
|
||||||
|
case Target.windows:
|
||||||
_buildDistributor(
|
_buildDistributor(
|
||||||
platform: platform,
|
target: target,
|
||||||
targets: "exe,zip",
|
targets: "exe,zip",
|
||||||
args: "--description ${arch!.name}",
|
args: "--description $archName",
|
||||||
);
|
);
|
||||||
case PlatformType.linux:
|
case Target.linux:
|
||||||
await _getLinuxDependencies();
|
await _getLinuxDependencies();
|
||||||
_buildDistributor(
|
_buildDistributor(
|
||||||
platform: platform,
|
target: target,
|
||||||
targets: "appimage,deb,rpm",
|
targets: "appimage,deb,rpm",
|
||||||
args: "--description ${arch!.name}",
|
args: "--description $archName",
|
||||||
);
|
);
|
||||||
case PlatformType.android:
|
case Target.android:
|
||||||
final targetMap = {
|
final targetMap = {
|
||||||
Arch.arm: "android-arm",
|
Arch.arm: "android-arm",
|
||||||
Arch.arm64: "android-arm64",
|
Arch.arm64: "android-arm64",
|
||||||
@@ -387,17 +459,17 @@ class BuildCommand extends Command {
|
|||||||
.map((e) => targetMap[e])
|
.map((e) => targetMap[e])
|
||||||
.toList();
|
.toList();
|
||||||
_buildDistributor(
|
_buildDistributor(
|
||||||
platform: platform,
|
target: target,
|
||||||
targets: "apk",
|
targets: "apk",
|
||||||
args:
|
args:
|
||||||
"--flutter-build-args split-per-abi --build-target-platform ${defaultTargets.join(",")}",
|
"--flutter-build-args split-per-abi --build-target-platform ${defaultTargets.join(",")}",
|
||||||
);
|
);
|
||||||
case PlatformType.macos:
|
case Target.macos:
|
||||||
await _getMacosDependencies();
|
await _getMacosDependencies();
|
||||||
_buildDistributor(
|
_buildDistributor(
|
||||||
platform: platform,
|
target: target,
|
||||||
targets: "dmg",
|
targets: "dmg",
|
||||||
args: "--description ${arch!.name}",
|
args: "--description $archName",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -405,15 +477,15 @@ class BuildCommand extends Command {
|
|||||||
|
|
||||||
main(args) async {
|
main(args) async {
|
||||||
final runner = CommandRunner("setup", "build Application");
|
final runner = CommandRunner("setup", "build Application");
|
||||||
runner.addCommand(BuildCommand(platform: PlatformType.android));
|
runner.addCommand(BuildCommand(target: Target.android));
|
||||||
if (Platform.isWindows) {
|
if (Platform.isWindows) {
|
||||||
runner.addCommand(BuildCommand(platform: PlatformType.windows));
|
runner.addCommand(BuildCommand(target: Target.windows));
|
||||||
}
|
}
|
||||||
if (Platform.isLinux) {
|
if (Platform.isLinux) {
|
||||||
runner.addCommand(BuildCommand(platform: PlatformType.linux));
|
runner.addCommand(BuildCommand(target: Target.linux));
|
||||||
}
|
}
|
||||||
if (Platform.isMacOS) {
|
if (Platform.isMacOS) {
|
||||||
runner.addCommand(BuildCommand(platform: PlatformType.macos));
|
runner.addCommand(BuildCommand(target: Target.macos));
|
||||||
}
|
}
|
||||||
runner.run(args);
|
runner.run(args);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,35 @@
|
|||||||
// ignore_for_file: avoid_print
|
// ignore_for_file: avoid_print
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
void main() {
|
Future<void> main() async {
|
||||||
final cmdList = [];
|
// final cmdList = [];
|
||||||
final ignoreHosts = "\"ass\"";
|
// final ignoreHosts = "\"ass\"";
|
||||||
cmdList.add(
|
// cmdList.add(
|
||||||
["gsettings", "set", "org.gnome.system.proxy", "port", ignoreHosts],
|
// ["gsettings", "set", "org.gnome.system.proxy", "port", ignoreHosts],
|
||||||
|
// );
|
||||||
|
// print(cmdList.first);
|
||||||
|
final internetAddress = InternetAddress(
|
||||||
|
"/tmp/FlClashSocket.sock",
|
||||||
|
type: InternetAddressType.unix,
|
||||||
);
|
);
|
||||||
print(cmdList.first);
|
|
||||||
|
final socket = await Socket.connect(internetAddress, 0);
|
||||||
|
socket
|
||||||
|
.transform(
|
||||||
|
StreamTransformer<Uint8List, String>.fromHandlers(
|
||||||
|
handleData: (Uint8List data, EventSink<String> sink) {
|
||||||
|
sink.add(utf8.decode(data));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.transform(LineSplitter())
|
||||||
|
.listen((res) {
|
||||||
|
print(res);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
startService() async {
|
startService() async {
|
||||||
|
|||||||
@@ -90,7 +90,10 @@ set(CLASH_DIR "../libclash/windows")
|
|||||||
# set(CLASH_DIR "../libclash/windows/x86")
|
# set(CLASH_DIR "../libclash/windows/x86")
|
||||||
# endif()
|
# endif()
|
||||||
|
|
||||||
install(FILES "${CLASH_DIR}/libclash.dll" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
install(PROGRAMS "${CLASH_DIR}/FlClashCore.exe" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||||
|
COMPONENT Runtime)
|
||||||
|
|
||||||
|
install(PROGRAMS "${CLASH_DIR}/FlClashHelperService.exe" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||||
COMPONENT Runtime)
|
COMPONENT Runtime)
|
||||||
|
|
||||||
install(FILES "EnableLoopback.exe" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
install(FILES "EnableLoopback.exe" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||||
|
|||||||
Reference in New Issue
Block a user