Compare commits
39 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ece8a48181 | ||
|
|
4b32a096dd | ||
|
|
04d6a928eb | ||
|
|
22e71ec6e1 | ||
|
|
6f1b07e26b | ||
|
|
1e3ab1c717 | ||
|
|
3a43dc2fe9 | ||
|
|
c94f64cf78 | ||
|
|
25764fc4d3 | ||
|
|
649dbb9886 | ||
|
|
e1dd616d74 | ||
|
|
c91d78b63f | ||
|
|
82767325e5 | ||
|
|
3f0f7f051b | ||
|
|
15c64327db | ||
|
|
09e35eb5dd | ||
|
|
77c4935106 | ||
|
|
e6da643186 | ||
|
|
61bd4e4549 | ||
|
|
a5f4d12748 | ||
|
|
9dc67f31a3 | ||
|
|
846ec9728f | ||
|
|
6e5f1b8e5f | ||
|
|
aca4a3e979 | ||
|
|
3783c3c650 | ||
|
|
01aed99002 | ||
|
|
9aec1e75e4 | ||
|
|
5dfb95a22d | ||
|
|
0f8cfa20b2 | ||
|
|
3933fd9d9e | ||
|
|
b7400f2ce8 | ||
|
|
d0ec580932 | ||
|
|
6bc0be9876 | ||
|
|
38221bcd10 | ||
|
|
bee2f8aa4f | ||
|
|
6cfcaa4edc | ||
|
|
3fdc69edd5 | ||
|
|
ed7ade0d72 | ||
|
|
621ddefc65 |
20
.github/workflows/build.yaml
vendored
20
.github/workflows/build.yaml
vendored
@@ -27,25 +27,6 @@ jobs:
|
||||
arch: arm64
|
||||
|
||||
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
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -103,7 +84,6 @@ jobs:
|
||||
path: ./dist
|
||||
overwrite: true
|
||||
|
||||
|
||||
upload:
|
||||
permissions: write-all
|
||||
needs: [ build ]
|
||||
|
||||
20
.github/workflows/change.yaml
vendored
20
.github/workflows/change.yaml
vendored
@@ -1,12 +1,13 @@
|
||||
name: change
|
||||
name: changelog
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
changelog:
|
||||
if: ${{ !contains(github.ref, '+') }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -31,17 +32,19 @@ jobs:
|
||||
fi
|
||||
fi
|
||||
if [ -n "$currentTag" ]; then
|
||||
echo "## $currentTag" >> CHANGELOG.md
|
||||
echo "" >> CHANGELOG.md
|
||||
echo "## $currentTag" >> NEW_CHANGELOG.md
|
||||
echo "" >> NEW_CHANGELOG.md
|
||||
if [ -n "$tag" ]; then
|
||||
git log --pretty=format:"%B" "$tag..$currentTag" | awk 'NF {print "- " $0} !NF {print ""}' >> CHANGELOG.md
|
||||
git log --pretty=format:"%B" "$tag..$currentTag" | awk 'NF {print "- " $0} !NF {print ""}' >> NEW_CHANGELOG.md
|
||||
else
|
||||
git log --pretty=format:"%B" "$currentTag" | awk 'NF {print "- " $0} !NF {print ""}' >> CHANGELOG.md
|
||||
git log --pretty=format:"%B" "$currentTag" | awk 'NF {print "- " $0} !NF {print ""}' >> NEW_CHANGELOG.md
|
||||
fi
|
||||
echo "" >> CHANGELOG.md
|
||||
echo "" >> NEW_CHANGELOG.md
|
||||
fi
|
||||
currentTag=$tag
|
||||
done
|
||||
cat CHANGELOG.md >> NEW_CHANGELOG.md
|
||||
cat NEW_CHANGELOG.md > CHANGELOG.md
|
||||
|
||||
- name: Commit
|
||||
run: |
|
||||
@@ -59,6 +62,5 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -495,7 +495,8 @@
|
||||
|
||||
## v0.8.12
|
||||
|
||||
- Fix the problem that the download of remote resources failed after GeodataMode was turned on, which caused the application to flash back.
|
||||
- Fix the problem that the download of remote resources failed after GeodataMode was turned on, which caused the
|
||||
application to flash back.
|
||||
|
||||
- Fix edit profile error
|
||||
|
||||
@@ -660,5 +661,4 @@
|
||||
|
||||
- update mobile_scanner
|
||||
|
||||
- Initial commit
|
||||
|
||||
- Initial commit
|
||||
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
|
||||
}
|
||||
156
core/common.go
156
core/common.go
@@ -1,23 +1,10 @@
|
||||
package main
|
||||
|
||||
import "C"
|
||||
import (
|
||||
"context"
|
||||
"core/state"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"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/inbound"
|
||||
"github.com/metacubex/mihomo/adapter/outboundgroup"
|
||||
@@ -27,53 +14,28 @@ import (
|
||||
"github.com/metacubex/mihomo/component/resolver"
|
||||
"github.com/metacubex/mihomo/config"
|
||||
"github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/constant/features"
|
||||
cp "github.com/metacubex/mihomo/constant/provider"
|
||||
"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/log"
|
||||
rp "github.com/metacubex/mihomo/rules/provider"
|
||||
"github.com/metacubex/mihomo/tunnel"
|
||||
"github.com/samber/lo"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
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"`
|
||||
}
|
||||
var (
|
||||
isRunning = false
|
||||
runLock sync.Mutex
|
||||
ips = []string{"ipinfo.io", "ipapi.co", "api.ip.sb", "ipwho.is"}
|
||||
b, _ = batch.New[bool](context.Background(), batch.WithConcurrencyNum[bool](50))
|
||||
)
|
||||
|
||||
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) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
|
||||
var b, _ = batch.New[bool](context.Background(), batch.WithConcurrencyNum[bool](50))
|
||||
|
||||
func restartExecutable(execPath string) {
|
||||
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 (message *Message) Json() (string, error) {
|
||||
data, err := json.Marshal(message)
|
||||
return string(data), err
|
||||
}
|
||||
|
||||
func readFile(path string) ([]byte, error) {
|
||||
@@ -119,19 +60,6 @@ func readFile(path string) ([]byte, error) {
|
||||
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 {
|
||||
return filepath.Join(constant.Path.HomeDir(), "profiles", id+".yaml")
|
||||
}
|
||||
@@ -262,8 +190,6 @@ func trimArr(arr []string) (r []string) {
|
||||
return
|
||||
}
|
||||
|
||||
var ips = []string{"ipinfo.io", "ipapi.co", "api.ip.sb", "ipwho.is"}
|
||||
|
||||
func overrideRules(rules *[]string) {
|
||||
var target = ""
|
||||
for _, line := range *rules {
|
||||
@@ -325,20 +251,13 @@ func overwriteConfig(targetConfig *config.RawConfig, patchConfig config.RawConfi
|
||||
}
|
||||
}
|
||||
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")
|
||||
general := currentConfig.General
|
||||
controller := currentConfig.Controller
|
||||
tls := currentConfig.TLS
|
||||
tunnel.SetSniffing(general.Sniffing)
|
||||
tunnel.SetFindProcessMode(general.FindProcessMode)
|
||||
dialer.SetTcpConcurrent(general.TCPConcurrent)
|
||||
@@ -365,17 +284,15 @@ func patchConfig(general *config.General, controller *config.Controller, tls *co
|
||||
})
|
||||
}
|
||||
|
||||
var isRunning = false
|
||||
|
||||
var runLock sync.Mutex
|
||||
|
||||
func updateListeners(general *config.General, listeners map[string]constant.InboundListener) {
|
||||
func updateListeners(force bool) {
|
||||
if !isRunning {
|
||||
return
|
||||
}
|
||||
runLock.Lock()
|
||||
defer runLock.Unlock()
|
||||
stopListeners()
|
||||
general := currentConfig.General
|
||||
listeners := currentConfig.Listeners
|
||||
if force == true {
|
||||
stopListeners()
|
||||
}
|
||||
listener.PatchInboundListeners(listeners, tunnel.Tunnel, true)
|
||||
listener.SetAllowLan(general.AllowLan)
|
||||
inbound.SetSkipAuthPrefixes(general.SkipAuthPrefixes)
|
||||
@@ -424,19 +341,22 @@ func patchSelectGroup() {
|
||||
}
|
||||
}
|
||||
|
||||
func applyConfig() error {
|
||||
cfg, err := config.ParseRawConfig(state.CurrentRawConfig)
|
||||
func applyConfig(rawConfig *config.RawConfig) error {
|
||||
runLock.Lock()
|
||||
defer runLock.Unlock()
|
||||
var err error
|
||||
currentConfig, err = config.ParseRawConfig(rawConfig)
|
||||
if err != nil {
|
||||
cfg, _ = config.ParseRawConfig(config.DefaultRawConfig())
|
||||
currentConfig, _ = config.ParseRawConfig(config.DefaultRawConfig())
|
||||
}
|
||||
if configParams.IsPatch {
|
||||
patchConfig(cfg.General, cfg.Controller, cfg.TLS)
|
||||
patchConfig()
|
||||
} else {
|
||||
closeConnections()
|
||||
handleCloseConnectionsUnLock()
|
||||
runtime.GC()
|
||||
hub.ApplyConfig(cfg)
|
||||
hub.ApplyConfig(currentConfig)
|
||||
patchSelectGroup()
|
||||
}
|
||||
updateListeners(cfg.General, cfg.Listeners)
|
||||
updateListeners(false)
|
||||
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
|
||||
|
||||
/*
|
||||
|
||||
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
|
||||
|
||||
go 1.21.0
|
||||
go 1.21
|
||||
|
||||
replace github.com/metacubex/mihomo => ./Clash.Meta
|
||||
|
||||
|
||||
433
core/hub.go
433
core/hub.go
@@ -1 +1,434 @@
|
||||
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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
601
core/lib.go
601
core/lib.go
@@ -1,3 +1,5 @@
|
||||
//go:build cgo
|
||||
|
||||
package main
|
||||
|
||||
/*
|
||||
@@ -5,430 +7,10 @@ package main
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"context"
|
||||
bridge "core/dart-bridge"
|
||||
"core/state"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/metacubex/mihomo/common/utils"
|
||||
"os"
|
||||
"runtime"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
"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
|
||||
func initNativeApiBridge(api unsafe.Pointer) {
|
||||
bridge.InitDartApi(api)
|
||||
@@ -445,31 +27,156 @@ func freeCString(s *C.char) {
|
||||
C.free(unsafe.Pointer(s))
|
||||
}
|
||||
|
||||
func init() {
|
||||
provider.HealthcheckHook = 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,
|
||||
})
|
||||
}
|
||||
//export initClash
|
||||
func initClash(homeDirStr *C.char) bool {
|
||||
return handleInitClash(C.GoString(homeDirStr))
|
||||
}
|
||||
|
||||
//export startListener
|
||||
func startListener() {
|
||||
handleStartListener()
|
||||
}
|
||||
|
||||
//export stopListener
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
||||
//export getTraffic
|
||||
func getTraffic(port C.int) *C.char {
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
||||
//export getConnections
|
||||
func getConnections() *C.char {
|
||||
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
|
||||
|
||||
import "C"
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
import (
|
||||
bridge "core/dart-bridge"
|
||||
"encoding/json"
|
||||
"github.com/metacubex/mihomo/constant"
|
||||
)
|
||||
|
||||
var Port int64
|
||||
var ServicePort int64
|
||||
|
||||
type MessageType string
|
||||
|
||||
const (
|
||||
LogMessage MessageType = "log"
|
||||
ProtectMessage MessageType = "protect"
|
||||
DelayMessage MessageType = "delay"
|
||||
ProcessMessage MessageType = "process"
|
||||
RequestMessage MessageType = "request"
|
||||
StartedMessage MessageType = "started"
|
||||
LoadedMessage MessageType = "loaded"
|
||||
)
|
||||
|
||||
type Delay struct {
|
||||
Name string `json:"name"`
|
||||
Value int32 `json:"value"`
|
||||
}
|
||||
|
||||
type Process struct {
|
||||
Id int64 `json:"id"`
|
||||
Metadata *constant.Metadata `json:"metadata"`
|
||||
}
|
||||
|
||||
type Message struct {
|
||||
Type MessageType `json:"type"`
|
||||
Data interface{} `json:"data"`
|
||||
}
|
||||
|
||||
func (message *Message) Json() (string, error) {
|
||||
data, err := json.Marshal(message)
|
||||
return string(data), err
|
||||
}
|
||||
|
||||
func SendMessage(message Message) {
|
||||
s, err := message.Json()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if handler, ok := messageHandlers[message.Type]; ok {
|
||||
handler(s)
|
||||
} else {
|
||||
sendToPort(s)
|
||||
}
|
||||
}
|
||||
|
||||
var messageHandlers = map[MessageType]func(string) bool{
|
||||
ProtectMessage: sendToServicePort,
|
||||
ProcessMessage: sendToServicePort,
|
||||
StartedMessage: conditionalSend,
|
||||
LoadedMessage: conditionalSend,
|
||||
}
|
||||
|
||||
func sendToPort(s string) bool {
|
||||
return bridge.SendToPort(Port, s)
|
||||
}
|
||||
|
||||
func sendToServicePort(s string) bool {
|
||||
return bridge.SendToPort(ServicePort, s)
|
||||
}
|
||||
|
||||
func conditionalSend(s string) bool {
|
||||
isSuccess := sendToPort(s)
|
||||
if !isSuccess {
|
||||
return sendToServicePort(s)
|
||||
}
|
||||
return isSuccess
|
||||
Action{
|
||||
Method: messageMethod,
|
||||
}.callback(s)
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
|
||||
@@ -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 DefaultDnsAddress = "172.19.0.2"
|
||||
var DefaultIpv6Address = "fdfe:dcba:9876::1/126"
|
||||
|
||||
var CurrentRawConfig = config.DefaultRawConfig()
|
||||
|
||||
type AndroidVpnOptions struct {
|
||||
Enable bool `json:"enable"`
|
||||
Port int `json:"port"`
|
||||
@@ -41,7 +39,6 @@ type AndroidVpnRawOptions struct {
|
||||
type State struct {
|
||||
AndroidVpnRawOptions
|
||||
CurrentProfileName string `json:"currentProfileName"`
|
||||
OnlyProxy bool `json:"onlyProxy"`
|
||||
}
|
||||
|
||||
var CurrentState = &State{}
|
||||
@@ -55,7 +52,5 @@ func GetIpv6Address() string {
|
||||
}
|
||||
|
||||
func GetDnsServerAddress() string {
|
||||
//prefix, _ := netip.ParsePrefix(DefaultIpv4Address)
|
||||
//return prefix.Addr().String()
|
||||
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
|
||||
|
||||
import "C"
|
||||
import (
|
||||
"core/state"
|
||||
"github.com/metacubex/mihomo/constant"
|
||||
LC "github.com/metacubex/mihomo/listener/config"
|
||||
"github.com/metacubex/mihomo/listener/sing_tun"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
@@ -23,7 +24,7 @@ type Props struct {
|
||||
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
|
||||
tempPrefix4, err := netip.ParsePrefix(state.DefaultIpv4Address)
|
||||
if err != nil {
|
||||
@@ -46,8 +47,8 @@ func Start(fd int) (*sing_tun.Listener, error) {
|
||||
|
||||
options := LC.Tun{
|
||||
Enable: true,
|
||||
Device: state.CurrentRawConfig.Tun.Device,
|
||||
Stack: state.CurrentRawConfig.Tun.Stack,
|
||||
Device: device,
|
||||
Stack: stack,
|
||||
DNSHijack: dnsHijack,
|
||||
AutoRoute: false,
|
||||
AutoDetectInterface: false,
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:animations/animations.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/l10n/l10n.dart';
|
||||
import 'package:fl_clash/manager/hotkey_manager.dart';
|
||||
import 'package:fl_clash/manager/manager.dart';
|
||||
import 'package:fl_clash/plugins/app.dart';
|
||||
@@ -245,8 +247,10 @@ class ApplicationState extends State<Application> {
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
linkManager.destroy();
|
||||
await globalState.appController.savePreferences();
|
||||
super.dispose();
|
||||
_cancelTimer();
|
||||
await clashService?.destroy();
|
||||
await globalState.appController.savePreferences();
|
||||
await globalState.appController.handleExit();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export 'core.dart';
|
||||
export 'lib.dart';
|
||||
export 'message.dart';
|
||||
export 'service.dart';
|
||||
export 'message.dart';
|
||||
@@ -1,42 +1,26 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:ffi';
|
||||
import 'dart:io';
|
||||
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/enum/enum.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
|
||||
import 'generated/clash_ffi.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:path/path.dart';
|
||||
|
||||
class ClashCore {
|
||||
static ClashCore? _instance;
|
||||
static final receiver = ReceivePort();
|
||||
|
||||
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";
|
||||
}
|
||||
late ClashInterface clashInterface;
|
||||
|
||||
ClashCore._internal() {
|
||||
lib = _getClashLib();
|
||||
clashFFI = ClashFFI(lib);
|
||||
clashFFI.initNativeApiBridge(
|
||||
NativeApi.initializeApiDLData,
|
||||
);
|
||||
if (Platform.isAndroid) {
|
||||
clashInterface = clashLib!;
|
||||
} else {
|
||||
clashInterface = clashService!;
|
||||
}
|
||||
}
|
||||
|
||||
factory ClashCore() {
|
||||
@@ -44,67 +28,62 @@ class ClashCore {
|
||||
return _instance!;
|
||||
}
|
||||
|
||||
bool init(String homeDir) {
|
||||
final homeDirChar = homeDir.toNativeUtf8().cast<Char>();
|
||||
final isInit = clashFFI.initClash(homeDirChar) == 1;
|
||||
malloc.free(homeDirChar);
|
||||
return isInit;
|
||||
}
|
||||
|
||||
shutdown() {
|
||||
clashFFI.shutdownClash();
|
||||
lib.close();
|
||||
}
|
||||
|
||||
bool get isInit => clashFFI.getIsInit() == 1;
|
||||
|
||||
Future<String> validateConfig(String data) {
|
||||
final completer = Completer<String>();
|
||||
final receiver = ReceivePort();
|
||||
receiver.listen((message) {
|
||||
if (!completer.isCompleted) {
|
||||
completer.complete(message);
|
||||
receiver.close();
|
||||
Future<void> _initGeo() async {
|
||||
final homePath = await appPath.getHomeDirPath();
|
||||
final homeDir = Directory(homePath);
|
||||
final isExists = await homeDir.exists();
|
||||
if (!isExists) {
|
||||
await homeDir.create(recursive: true);
|
||||
}
|
||||
const geoFileNameList = [
|
||||
mmdbFileName,
|
||||
geoIpFileName,
|
||||
geoSiteFileName,
|
||||
asnFileName,
|
||||
];
|
||||
try {
|
||||
for (final geoFileName in geoFileNameList) {
|
||||
final geoFile = File(
|
||||
join(homePath, geoFileName),
|
||||
);
|
||||
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);
|
||||
}
|
||||
});
|
||||
final dataChar = data.toNativeUtf8().cast<Char>();
|
||||
clashFFI.validateConfig(
|
||||
dataChar,
|
||||
receiver.sendPort.nativePort,
|
||||
);
|
||||
malloc.free(dataChar);
|
||||
return completer.future;
|
||||
} catch (e) {
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
Future<bool> init({
|
||||
required ClashConfig clashConfig,
|
||||
required Config config,
|
||||
}) async {
|
||||
await _initGeo();
|
||||
final homeDirPath = await appPath.getHomeDirPath();
|
||||
return await clashInterface.init(homeDirPath);
|
||||
}
|
||||
|
||||
initMessage() {
|
||||
clashFFI.initMessage(
|
||||
receiver.sendPort.nativePort,
|
||||
);
|
||||
shutdown() async {
|
||||
await clashInterface.shutdown();
|
||||
}
|
||||
|
||||
Future<List<Group>> getProxiesGroups() {
|
||||
final proxiesRaw = clashFFI.getProxies();
|
||||
final proxiesRawString = proxiesRaw.cast<Utf8>().toDartString();
|
||||
clashFFI.freeCString(proxiesRaw);
|
||||
FutureOr<bool> get isInit => clashInterface.isInit;
|
||||
|
||||
FutureOr<String> validateConfig(String data) {
|
||||
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>>(() {
|
||||
if (proxiesRawString.isEmpty) return [];
|
||||
final proxies = (json.decode(proxiesRawString) ?? {}) as Map;
|
||||
@@ -134,256 +113,112 @@ class ClashCore {
|
||||
});
|
||||
}
|
||||
|
||||
Future<List<ExternalProvider>> getExternalProviders() {
|
||||
final externalProvidersRaw = clashFFI.getExternalProviders();
|
||||
final externalProvidersRawString =
|
||||
externalProvidersRaw.cast<Utf8>().toDartString();
|
||||
clashFFI.freeCString(externalProvidersRaw);
|
||||
return Isolate.run<List<ExternalProvider>>(() {
|
||||
final externalProviders =
|
||||
(json.decode(externalProvidersRawString) as List<dynamic>)
|
||||
.map(
|
||||
(item) => ExternalProvider.fromJson(item),
|
||||
)
|
||||
.toList();
|
||||
return externalProviders;
|
||||
});
|
||||
FutureOr<String> changeProxy(ChangeProxyParams changeProxyParams) async {
|
||||
return await clashInterface.changeProxy(changeProxyParams);
|
||||
}
|
||||
|
||||
ExternalProvider? 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);
|
||||
if (externalProviderRawString.isEmpty) return null;
|
||||
return ExternalProvider.fromJson(json.decode(externalProviderRawString));
|
||||
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 =
|
||||
await clashInterface.getExternalProviders();
|
||||
return Isolate.run<List<ExternalProvider>>(
|
||||
() {
|
||||
final externalProviders =
|
||||
(json.decode(externalProvidersRawString) as List<dynamic>)
|
||||
.map(
|
||||
(item) => ExternalProvider.fromJson(item),
|
||||
)
|
||||
.toList();
|
||||
return externalProviders;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<ExternalProvider?> getExternalProvider(
|
||||
String externalProviderName) async {
|
||||
final externalProvidersRawString =
|
||||
await clashInterface.getExternalProvider(externalProviderName);
|
||||
if (externalProvidersRawString == null) {
|
||||
return null;
|
||||
}
|
||||
if (externalProvidersRawString.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
return ExternalProvider.fromJson(json.decode(externalProvidersRawString));
|
||||
}
|
||||
|
||||
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;
|
||||
return clashInterface.updateGeoData(geoType: geoType, geoName: geoName);
|
||||
}
|
||||
|
||||
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;
|
||||
return clashInterface.sideLoadExternalProvider(
|
||||
providerName: providerName, data: data);
|
||||
}
|
||||
|
||||
Future<String> updateExternalProvider({
|
||||
required 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;
|
||||
}) async {
|
||||
return clashInterface.updateExternalProvider(providerName);
|
||||
}
|
||||
|
||||
changeProxy(ChangeProxyParams changeProxyParams) {
|
||||
final params = json.encode(changeProxyParams);
|
||||
final paramsChar = params.toNativeUtf8().cast<Char>();
|
||||
clashFFI.changeProxy(paramsChar);
|
||||
malloc.free(paramsChar);
|
||||
startListener() async {
|
||||
await clashInterface.startListener();
|
||||
}
|
||||
|
||||
start() {
|
||||
clashFFI.start();
|
||||
stopListener() async {
|
||||
await clashInterface.stopListener();
|
||||
}
|
||||
|
||||
stop() {
|
||||
clashFFI.stop();
|
||||
Future<Delay> getDelay(String proxyName) async {
|
||||
final data = await clashInterface.asyncTestDelay(proxyName);
|
||||
return Delay.fromJson(json.decode(data));
|
||||
}
|
||||
|
||||
Future<Delay> getDelay(String proxyName) {
|
||||
final delayParams = {
|
||||
"proxy-name": proxyName,
|
||||
"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;
|
||||
Future<Traffic> getTraffic(bool value) async {
|
||||
final trafficString = await clashInterface.getTraffic(value);
|
||||
return Traffic.fromMap(json.decode(trafficString));
|
||||
}
|
||||
|
||||
clearEffect(String profileId) {
|
||||
final profileIdChar = profileId.toNativeUtf8().cast<Char>();
|
||||
clashFFI.clearEffect(profileIdChar);
|
||||
malloc.free(profileIdChar);
|
||||
Future<Traffic> getTotalTraffic(bool value) async {
|
||||
final totalTrafficString = await clashInterface.getTotalTraffic(value);
|
||||
return Traffic.fromMap(json.decode(totalTrafficString));
|
||||
}
|
||||
|
||||
VersionInfo getVersionInfo() {
|
||||
final versionInfoRaw = clashFFI.getVersionInfo();
|
||||
final versionInfo = json.decode(versionInfoRaw.cast<Utf8>().toDartString());
|
||||
clashFFI.freeCString(versionInfoRaw);
|
||||
return VersionInfo.fromJson(versionInfo);
|
||||
resetTraffic() {
|
||||
clashInterface.resetTraffic();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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();
|
||||
startLog() {
|
||||
clashInterface.startLog();
|
||||
}
|
||||
|
||||
stopLog() {
|
||||
clashFFI.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);
|
||||
clashInterface.stopLog();
|
||||
}
|
||||
|
||||
requestGc() {
|
||||
clashFFI.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();
|
||||
clashInterface.forceGc();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2362,21 +2362,46 @@ class ClashFFI {
|
||||
late final _updateDns =
|
||||
_updateDnsPtr.asFunction<void Function(ffi.Pointer<ffi.Char>)>();
|
||||
|
||||
void start() {
|
||||
return _start();
|
||||
void initNativeApiBridge(
|
||||
ffi.Pointer<ffi.Void> api,
|
||||
) {
|
||||
return _initNativeApiBridge(
|
||||
api,
|
||||
);
|
||||
}
|
||||
|
||||
late final _startPtr =
|
||||
_lookup<ffi.NativeFunction<ffi.Void Function()>>('start');
|
||||
late final _start = _startPtr.asFunction<void Function()>();
|
||||
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 stop() {
|
||||
return _stop();
|
||||
void initMessage(
|
||||
int port,
|
||||
) {
|
||||
return _initMessage(
|
||||
port,
|
||||
);
|
||||
}
|
||||
|
||||
late final _stopPtr =
|
||||
_lookup<ffi.NativeFunction<ffi.Void Function()>>('stop');
|
||||
late final _stop = _stopPtr.asFunction<void Function()>();
|
||||
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>)>();
|
||||
|
||||
int initClash(
|
||||
ffi.Pointer<ffi.Char> homeDirStr,
|
||||
@@ -2392,6 +2417,22 @@ class ClashFFI {
|
||||
late final _initClash =
|
||||
_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() {
|
||||
return _getIsInit();
|
||||
}
|
||||
@@ -2400,14 +2441,6 @@ class ClashFFI {
|
||||
_lookup<ffi.NativeFunction<GoUint8 Function()>>('getIsInit');
|
||||
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() {
|
||||
return _shutdownClash();
|
||||
}
|
||||
@@ -2458,20 +2491,6 @@ class ClashFFI {
|
||||
late final _updateConfig =
|
||||
_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() {
|
||||
return _getProxies();
|
||||
}
|
||||
@@ -2484,37 +2503,48 @@ class ClashFFI {
|
||||
|
||||
void changeProxy(
|
||||
ffi.Pointer<ffi.Char> s,
|
||||
int port,
|
||||
) {
|
||||
return _changeProxy(
|
||||
s,
|
||||
port,
|
||||
);
|
||||
}
|
||||
|
||||
late final _changeProxyPtr =
|
||||
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Char>)>>(
|
||||
'changeProxy');
|
||||
late final _changeProxyPtr = _lookup<
|
||||
ffi.NativeFunction<
|
||||
ffi.Void Function(
|
||||
ffi.Pointer<ffi.Char>, ffi.LongLong)>>('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() {
|
||||
return _getTraffic();
|
||||
ffi.Pointer<ffi.Char> getTraffic(
|
||||
int port,
|
||||
) {
|
||||
return _getTraffic(
|
||||
port,
|
||||
);
|
||||
}
|
||||
|
||||
late final _getTrafficPtr =
|
||||
_lookup<ffi.NativeFunction<ffi.Pointer<ffi.Char> Function()>>(
|
||||
_lookup<ffi.NativeFunction<ffi.Pointer<ffi.Char> Function(ffi.Int)>>(
|
||||
'getTraffic');
|
||||
late final _getTraffic =
|
||||
_getTrafficPtr.asFunction<ffi.Pointer<ffi.Char> Function()>();
|
||||
_getTrafficPtr.asFunction<ffi.Pointer<ffi.Char> Function(int)>();
|
||||
|
||||
ffi.Pointer<ffi.Char> getTotalTraffic() {
|
||||
return _getTotalTraffic();
|
||||
ffi.Pointer<ffi.Char> getTotalTraffic(
|
||||
int port,
|
||||
) {
|
||||
return _getTotalTraffic(
|
||||
port,
|
||||
);
|
||||
}
|
||||
|
||||
late final _getTotalTrafficPtr =
|
||||
_lookup<ffi.NativeFunction<ffi.Pointer<ffi.Char> Function()>>(
|
||||
_lookup<ffi.NativeFunction<ffi.Pointer<ffi.Char> Function(ffi.Int)>>(
|
||||
'getTotalTraffic');
|
||||
late final _getTotalTraffic =
|
||||
_getTotalTrafficPtr.asFunction<ffi.Pointer<ffi.Char> Function()>();
|
||||
_getTotalTrafficPtr.asFunction<ffi.Pointer<ffi.Char> Function(int)>();
|
||||
|
||||
void resetTraffic() {
|
||||
return _resetTraffic();
|
||||
@@ -2541,16 +2571,6 @@ class ClashFFI {
|
||||
late final _asyncTestDelay = _asyncTestDelayPtr
|
||||
.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() {
|
||||
return _getConnections();
|
||||
}
|
||||
@@ -2595,10 +2615,10 @@ class ClashFFI {
|
||||
_getExternalProvidersPtr.asFunction<ffi.Pointer<ffi.Char> Function()>();
|
||||
|
||||
ffi.Pointer<ffi.Char> getExternalProvider(
|
||||
ffi.Pointer<ffi.Char> name,
|
||||
ffi.Pointer<ffi.Char> externalProviderNameChar,
|
||||
) {
|
||||
return _getExternalProvider(
|
||||
name,
|
||||
externalProviderNameChar,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2610,13 +2630,13 @@ class ClashFFI {
|
||||
.asFunction<ffi.Pointer<ffi.Char> Function(ffi.Pointer<ffi.Char>)>();
|
||||
|
||||
void updateGeoData(
|
||||
ffi.Pointer<ffi.Char> geoType,
|
||||
ffi.Pointer<ffi.Char> geoName,
|
||||
ffi.Pointer<ffi.Char> geoTypeChar,
|
||||
ffi.Pointer<ffi.Char> geoNameChar,
|
||||
int port,
|
||||
) {
|
||||
return _updateGeoData(
|
||||
geoType,
|
||||
geoName,
|
||||
geoTypeChar,
|
||||
geoNameChar,
|
||||
port,
|
||||
);
|
||||
}
|
||||
@@ -2629,11 +2649,11 @@ class ClashFFI {
|
||||
void Function(ffi.Pointer<ffi.Char>, ffi.Pointer<ffi.Char>, int)>();
|
||||
|
||||
void updateExternalProvider(
|
||||
ffi.Pointer<ffi.Char> providerName,
|
||||
ffi.Pointer<ffi.Char> providerNameChar,
|
||||
int port,
|
||||
) {
|
||||
return _updateExternalProvider(
|
||||
providerName,
|
||||
providerNameChar,
|
||||
port,
|
||||
);
|
||||
}
|
||||
@@ -2646,13 +2666,13 @@ class ClashFFI {
|
||||
.asFunction<void Function(ffi.Pointer<ffi.Char>, int)>();
|
||||
|
||||
void sideLoadExternalProvider(
|
||||
ffi.Pointer<ffi.Char> providerName,
|
||||
ffi.Pointer<ffi.Char> data,
|
||||
ffi.Pointer<ffi.Char> providerNameChar,
|
||||
ffi.Pointer<ffi.Char> dataChar,
|
||||
int port,
|
||||
) {
|
||||
return _sideLoadExternalProvider(
|
||||
providerName,
|
||||
data,
|
||||
providerNameChar,
|
||||
dataChar,
|
||||
port,
|
||||
);
|
||||
}
|
||||
@@ -2665,47 +2685,6 @@ class ClashFFI {
|
||||
_sideLoadExternalProviderPtr.asFunction<
|
||||
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() {
|
||||
return _startLog();
|
||||
}
|
||||
@@ -2722,6 +2701,51 @@ class ClashFFI {
|
||||
_lookup<ffi.NativeFunction<ffi.Void Function()>>('stopLog');
|
||||
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(
|
||||
ffi.Pointer<ffi.Char> s,
|
||||
) {
|
||||
@@ -2769,51 +2793,6 @@ class ClashFFI {
|
||||
'setState');
|
||||
late final _setState =
|
||||
_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 {
|
||||
|
||||
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,42 +1,40 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:fl_clash/clash/clash.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import 'core.dart';
|
||||
|
||||
class ClashMessage {
|
||||
StreamSubscription? subscription;
|
||||
final controller = StreamController();
|
||||
|
||||
ClashMessage._() {
|
||||
if (subscription != null) {
|
||||
subscription!.cancel();
|
||||
subscription = null;
|
||||
}
|
||||
subscription = ClashCore.receiver.listen((message) {
|
||||
final m = AppMessage.fromJson(json.decode(message));
|
||||
for (final AppMessageListener listener in _listeners) {
|
||||
switch (m.type) {
|
||||
case AppMessageType.log:
|
||||
listener.onLog(Log.fromJson(m.data));
|
||||
break;
|
||||
case AppMessageType.delay:
|
||||
listener.onDelay(Delay.fromJson(m.data));
|
||||
break;
|
||||
case AppMessageType.request:
|
||||
listener.onRequest(Connection.fromJson(m.data));
|
||||
break;
|
||||
case AppMessageType.started:
|
||||
listener.onStarted(m.data);
|
||||
break;
|
||||
case AppMessageType.loaded:
|
||||
listener.onLoaded(m.data);
|
||||
break;
|
||||
clashLib?.receiver.listen(controller.add);
|
||||
controller.stream.listen(
|
||||
(message) {
|
||||
final m = AppMessage.fromJson(json.decode(message));
|
||||
for (final AppMessageListener listener in _listeners) {
|
||||
switch (m.type) {
|
||||
case AppMessageType.log:
|
||||
listener.onLog(Log.fromJson(m.data));
|
||||
break;
|
||||
case AppMessageType.delay:
|
||||
listener.onDelay(Delay.fromJson(m.data));
|
||||
break;
|
||||
case AppMessageType.request:
|
||||
listener.onRequest(Connection.fromJson(m.data));
|
||||
break;
|
||||
case AppMessageType.started:
|
||||
listener.onStarted(m.data);
|
||||
break;
|
||||
case AppMessageType.loaded:
|
||||
listener.onLoaded(m.data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
static final ClashMessage instance = ClashMessage._();
|
||||
|
||||
@@ -1,54 +1,414 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
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/models/models.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/models/core.dart';
|
||||
|
||||
import 'core.dart';
|
||||
class ClashService with ClashInterface {
|
||||
static ClashService? _instance;
|
||||
|
||||
class ClashService {
|
||||
Future<void> initGeo() async {
|
||||
final homePath = await appPath.getHomeDirPath();
|
||||
final homeDir = Directory(homePath);
|
||||
final isExists = await homeDir.exists();
|
||||
if (!isExists) {
|
||||
await homeDir.create(recursive: true);
|
||||
}
|
||||
const geoFileNameList = [
|
||||
mmdbFileName,
|
||||
geoIpFileName,
|
||||
geoSiteFileName,
|
||||
asnFileName,
|
||||
];
|
||||
try {
|
||||
for (final geoFileName in geoFileNameList) {
|
||||
final geoFile = File(
|
||||
join(homePath, geoFileName),
|
||||
);
|
||||
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);
|
||||
Completer<ServerSocket> serverCompleter = Completer();
|
||||
|
||||
Completer<Socket> socketCompleter = Completer();
|
||||
|
||||
Map<String, Completer> callbackCompleterMap = {};
|
||||
|
||||
Process? process;
|
||||
|
||||
factory ClashService() {
|
||||
_instance ??= ClashService._internal();
|
||||
return _instance!;
|
||||
}
|
||||
|
||||
ClashService._internal() {
|
||||
_createServer();
|
||||
startCore();
|
||||
}
|
||||
|
||||
_createServer() async {
|
||||
final address = !Platform.isWindows
|
||||
? InternetAddress(
|
||||
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()),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> init({
|
||||
required ClashConfig clashConfig,
|
||||
required Config config,
|
||||
startCore() async {
|
||||
if (process != null) {
|
||||
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 {
|
||||
await initGeo();
|
||||
final homeDirPath = await appPath.getHomeDirPath();
|
||||
final isInit = clashCore.init(homeDirPath);
|
||||
return isInit;
|
||||
final id = "${method.name}#${other.id}";
|
||||
final socket = await socketCompleter.future;
|
||||
callbackCompleterMap[id] = Completer<T>();
|
||||
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 '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 'color.dart';
|
||||
export 'constant.dart';
|
||||
export 'context.dart';
|
||||
export 'datetime.dart';
|
||||
export 'function.dart';
|
||||
export 'package.dart';
|
||||
export 'measure.dart';
|
||||
export 'windows.dart';
|
||||
export 'iterable.dart';
|
||||
export 'scroll.dart';
|
||||
export 'icons.dart';
|
||||
export 'future.dart';
|
||||
export 'http.dart';
|
||||
export 'icons.dart';
|
||||
export 'iterable.dart';
|
||||
export 'keyboard.dart';
|
||||
export 'launch.dart';
|
||||
export 'link.dart';
|
||||
export 'list.dart';
|
||||
export 'lock.dart';
|
||||
export 'measure.dart';
|
||||
export 'navigation.dart';
|
||||
export 'navigator.dart';
|
||||
export 'network.dart';
|
||||
export 'navigator.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:math';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'system.dart';
|
||||
|
||||
const appName = "FlClash";
|
||||
const appHelperService = "FlClashHelperService";
|
||||
const coreName = "clash.meta";
|
||||
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 moreDuration = Duration(milliseconds: 100);
|
||||
const animateDuration = Duration(milliseconds: 100);
|
||||
@@ -21,7 +26,7 @@ const geoSiteFileName = "GeoSite.dat";
|
||||
final double kHeaderHeight = system.isDesktop
|
||||
? !Platform.isMacOS
|
||||
? 40
|
||||
: 26
|
||||
: 28
|
||||
: 0;
|
||||
const GeoXMap defaultGeoXMap = {
|
||||
"mmdb":
|
||||
|
||||
@@ -24,8 +24,8 @@ class DAVClient {
|
||||
},
|
||||
);
|
||||
client.setConnectTimeout(8000);
|
||||
client.setSendTimeout(8000);
|
||||
client.setReceiveTimeout(8000);
|
||||
client.setSendTimeout(60000);
|
||||
client.setReceiveTimeout(60000);
|
||||
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 '../state.dart';
|
||||
import 'constant.dart';
|
||||
|
||||
class FlClashHttpOverrides extends HttpOverrides {
|
||||
@override
|
||||
@@ -10,6 +11,9 @@ class FlClashHttpOverrides extends HttpOverrides {
|
||||
final client = super.createHttpClient(context);
|
||||
client.badCertificateCallback = (_, __, ___) => true;
|
||||
client.findProxy = (url) {
|
||||
if ([localhost].contains(url.host)) {
|
||||
return "DIRECT";
|
||||
}
|
||||
debugPrint("find $url");
|
||||
final appController = globalState.appController;
|
||||
final port = appController.clashConfig.mixedPort;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import 'dart:async';
|
||||
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 'constant.dart';
|
||||
import 'system.dart';
|
||||
import 'windows.dart';
|
||||
|
||||
class AutoLaunch {
|
||||
static AutoLaunch? _instance;
|
||||
@@ -26,60 +26,16 @@ class AutoLaunch {
|
||||
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 {
|
||||
if (Platform.isWindows) {
|
||||
await windowsDisable();
|
||||
}
|
||||
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 {
|
||||
return await launchAtStartup.disable();
|
||||
}
|
||||
|
||||
updateStatus(AutoLaunchState state) async {
|
||||
final isAdminAutoLaunch = state.isAdminAutoLaunch;
|
||||
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 (isAutoLaunch == true) {
|
||||
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/enum/enum.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:image/image.dart' as img;
|
||||
import 'package:lpinyin/lpinyin.dart';
|
||||
import 'package:zxing2/qrcode.dart';
|
||||
import 'package:image/image.dart' as img;
|
||||
|
||||
class Other {
|
||||
Color? getDelayColor(int? delay) {
|
||||
@@ -19,6 +19,14 @@ class Other {
|
||||
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) {
|
||||
var valueRaw = "0$value";
|
||||
return valueRaw.substring(
|
||||
@@ -104,7 +112,7 @@ class Other {
|
||||
String getTrayIconPath({
|
||||
required Brightness brightness,
|
||||
}) {
|
||||
if(Platform.isMacOS){
|
||||
if (Platform.isMacOS) {
|
||||
return "assets/images/icon_white.png";
|
||||
}
|
||||
final suffix = Platform.isWindows ? "ico" : "png";
|
||||
|
||||
@@ -13,34 +13,17 @@ class AppPath {
|
||||
Completer<Directory> tempDir = Completer();
|
||||
late String appDirPath;
|
||||
|
||||
// Future<Directory> _createDesktopCacheDir() async {
|
||||
// final dir = Directory(path);
|
||||
// if (await dir.exists()) {
|
||||
// await dir.create(recursive: true);
|
||||
// }
|
||||
// return dir;
|
||||
// }
|
||||
|
||||
AppPath._internal() {
|
||||
appDirPath = join(dirname(Platform.resolvedExecutable));
|
||||
getApplicationSupportDirectory().then((value) {
|
||||
dataDir.complete(value);
|
||||
});
|
||||
getTemporaryDirectory().then((value){
|
||||
tempDir.complete(value);
|
||||
getTemporaryDirectory().then((value) {
|
||||
tempDir.complete(value);
|
||||
});
|
||||
getDownloadsDirectory().then((value) {
|
||||
downloadDir.complete(value);
|
||||
});
|
||||
// if (Platform.isAndroid) {
|
||||
// getApplicationSupportDirectory().then((value) {
|
||||
// cacheDir.complete(value);
|
||||
// });
|
||||
// } else {
|
||||
// _createDesktopCacheDir().then((value) {
|
||||
// cacheDir.complete(value);
|
||||
// });
|
||||
// }
|
||||
}
|
||||
|
||||
factory AppPath() {
|
||||
@@ -48,6 +31,23 @@ class AppPath {
|
||||
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 {
|
||||
final directory = await downloadDir.future;
|
||||
return directory.path;
|
||||
@@ -58,6 +58,11 @@ class AppPath {
|
||||
return directory.path;
|
||||
}
|
||||
|
||||
Future<String> getLockFilePath() async {
|
||||
final directory = await dataDir.future;
|
||||
return join(directory.path, "FlClash.lock");
|
||||
}
|
||||
|
||||
Future<String> getProfilesPath() async {
|
||||
final directory = await dataDir.future;
|
||||
return join(directory.path, profilesDirectoryName);
|
||||
@@ -69,6 +74,12 @@ class AppPath {
|
||||
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 {
|
||||
final directory = await tempDir.future;
|
||||
return directory.path;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
@@ -79,25 +81,96 @@ class Request {
|
||||
for (final source in _ipInfoSources.entries) {
|
||||
try {
|
||||
final response = await _dio
|
||||
.get<Map<String, dynamic>>(
|
||||
source.key,
|
||||
cancelToken: cancelToken,
|
||||
)
|
||||
.timeout(
|
||||
httpTimeoutDuration,
|
||||
);
|
||||
if (response.statusCode == 200 && response.data != null) {
|
||||
return source.value(response.data!);
|
||||
.get<Map<String, dynamic>>(source.key, cancelToken: cancelToken)
|
||||
.timeout(httpTimeoutDuration);
|
||||
if (response.statusCode != 200 || response.data == null) {
|
||||
continue;
|
||||
}
|
||||
return source.value(response.data!);
|
||||
} catch (e) {
|
||||
if (cancelToken?.isCancelled == true) {
|
||||
if (e is DioException && e.type == DioExceptionType.cancel) {
|
||||
throw "cancelled";
|
||||
}
|
||||
continue;
|
||||
debugPrint("checkIp error ===> $e");
|
||||
}
|
||||
}
|
||||
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();
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import 'dart:io';
|
||||
|
||||
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/state.dart';
|
||||
import 'package:fl_clash/widgets/input.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'window.dart';
|
||||
|
||||
class System {
|
||||
static System? _instance;
|
||||
|
||||
@@ -19,12 +21,6 @@ class System {
|
||||
bool get isDesktop =>
|
||||
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 {
|
||||
final deviceInfo = await DeviceInfoPlugin().deviceInfo;
|
||||
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 {
|
||||
await app?.moveTaskToBack();
|
||||
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:flutter/material.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
import 'package:windows_single_instance/windows_single_instance.dart';
|
||||
|
||||
class Window {
|
||||
init(WindowProps props, int version) async {
|
||||
final acquire = await singleInstanceLock.acquire();
|
||||
if (!acquire) {
|
||||
exit(0);
|
||||
}
|
||||
if (Platform.isWindows) {
|
||||
await WindowsSingleInstance.ensureSingleInstance([], "FlClash");
|
||||
protocol.register("clash");
|
||||
protocol.register("clashmeta");
|
||||
protocol.register("flclash");
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import 'dart:ffi';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:ffi/ffi.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';
|
||||
|
||||
class Windows {
|
||||
@@ -51,12 +54,84 @@ class Windows {
|
||||
calloc.free(argumentsPtr);
|
||||
calloc.free(operationPtr);
|
||||
|
||||
if (result <= 32) {
|
||||
debugPrint("[Windows] runas: $command $arguments resultCode:$result");
|
||||
|
||||
if (result < 42) {
|
||||
return false;
|
||||
}
|
||||
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 {
|
||||
final taskXml = '''
|
||||
<?xml version="1.0" encoding="UTF-16"?>
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'dart:isolate';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:archive/archive.dart';
|
||||
import 'package:fl_clash/clash/clash.dart';
|
||||
import 'package:fl_clash/common/archive.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
@@ -13,7 +14,6 @@ import 'package:path/path.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import 'clash/core.dart';
|
||||
import 'common/common.dart';
|
||||
import 'models/models.dart';
|
||||
|
||||
@@ -28,6 +28,7 @@ class AppController {
|
||||
late Function addCheckIpNumDebounce;
|
||||
late Function applyProfileDebounce;
|
||||
late Function savePreferencesDebounce;
|
||||
late Function changeProxyDebounce;
|
||||
|
||||
AppController(this.context) {
|
||||
appState = context.read<AppState>();
|
||||
@@ -43,6 +44,13 @@ class AppController {
|
||||
applyProfileDebounce = debounce<Function()>(() async {
|
||||
await applyProfile(isPrue: true);
|
||||
});
|
||||
changeProxyDebounce = debounce((String groupName, String proxyName) async {
|
||||
await changeProxy(
|
||||
groupName: groupName,
|
||||
proxyName: proxyName,
|
||||
);
|
||||
await updateGroups();
|
||||
});
|
||||
addCheckIpNumDebounce = debounce(() {
|
||||
appState.checkIpNum++;
|
||||
});
|
||||
@@ -51,6 +59,14 @@ class AppController {
|
||||
});
|
||||
}
|
||||
|
||||
restartCore() async {
|
||||
await globalState.restartCore(
|
||||
appState: appState,
|
||||
clashConfig: clashConfig,
|
||||
config: config,
|
||||
);
|
||||
}
|
||||
|
||||
updateStatus(bool isStart) async {
|
||||
if (isStart) {
|
||||
await globalState.handleStart();
|
||||
@@ -60,23 +76,31 @@ class AppController {
|
||||
updateRunTime,
|
||||
updateTraffic,
|
||||
];
|
||||
if (!Platform.isAndroid) {
|
||||
applyProfileDebounce();
|
||||
final currentLastModified =
|
||||
await config.getCurrentProfile()?.profileLastModified;
|
||||
if (currentLastModified == null ||
|
||||
globalState.lastProfileModified == null) {
|
||||
addCheckIpNumDebounce();
|
||||
return;
|
||||
}
|
||||
if (currentLastModified <= (globalState.lastProfileModified ?? 0)) {
|
||||
addCheckIpNumDebounce();
|
||||
return;
|
||||
}
|
||||
applyProfileDebounce();
|
||||
} else {
|
||||
await globalState.handleStop();
|
||||
clashCore.resetTraffic();
|
||||
await clashCore.resetTraffic();
|
||||
appFlowingState.traffics = [];
|
||||
appFlowingState.totalTraffic = Traffic();
|
||||
appFlowingState.runTime = null;
|
||||
await Future.delayed(
|
||||
Duration(milliseconds: 300),
|
||||
);
|
||||
addCheckIpNumDebounce();
|
||||
}
|
||||
}
|
||||
|
||||
updateCoreVersionInfo() {
|
||||
globalState.updateCoreVersionInfo(appState);
|
||||
}
|
||||
|
||||
updateRunTime() {
|
||||
final startTime = globalState.startTime;
|
||||
if (startTime != null) {
|
||||
@@ -90,6 +114,7 @@ class AppController {
|
||||
|
||||
updateTraffic() {
|
||||
globalState.updateTraffic(
|
||||
config: config,
|
||||
appFlowingState: appFlowingState,
|
||||
);
|
||||
}
|
||||
@@ -102,7 +127,7 @@ class AppController {
|
||||
|
||||
deleteProfile(String id) async {
|
||||
config.deleteProfileById(id);
|
||||
clashCore.clearEffect(id);
|
||||
clearEffect(id);
|
||||
if (config.currentProfileId == id) {
|
||||
if (config.profiles.isNotEmpty) {
|
||||
final updateId = config.profiles.first.id;
|
||||
@@ -130,6 +155,7 @@ class AppController {
|
||||
if (commonScaffoldState?.mounted != true) return;
|
||||
await commonScaffoldState?.loadingRun(() async {
|
||||
await globalState.updateClashConfig(
|
||||
appState: appState,
|
||||
clashConfig: clashConfig,
|
||||
config: config,
|
||||
isPatch: isPatch,
|
||||
@@ -213,8 +239,8 @@ class AppController {
|
||||
changeProxy({
|
||||
required String groupName,
|
||||
required String proxyName,
|
||||
}) {
|
||||
globalState.changeProxy(
|
||||
}) async {
|
||||
await globalState.changeProxy(
|
||||
config: config,
|
||||
groupName: groupName,
|
||||
proxyName: proxyName,
|
||||
@@ -234,22 +260,16 @@ class AppController {
|
||||
}
|
||||
|
||||
handleExit() async {
|
||||
await updateStatus(false);
|
||||
await proxy?.stopProxy();
|
||||
await savePreferences();
|
||||
clashCore.shutdown();
|
||||
try {
|
||||
await updateStatus(false);
|
||||
await clashCore.shutdown();
|
||||
await clashService?.destroy();
|
||||
await proxy?.stopProxy();
|
||||
await savePreferences();
|
||||
} catch (_) {}
|
||||
system.exit();
|
||||
}
|
||||
|
||||
updateLogStatus() {
|
||||
if (config.appSetting.openLogs) {
|
||||
clashCore.startLog();
|
||||
} else {
|
||||
clashCore.stopLog();
|
||||
appFlowingState.logs = [];
|
||||
}
|
||||
}
|
||||
|
||||
autoCheckUpdate() async {
|
||||
if (!config.appSetting.autoCheckUpdate) return;
|
||||
final res = await request.checkForUpdate();
|
||||
@@ -304,10 +324,20 @@ class AppController {
|
||||
if (!isDisclaimerAccepted) {
|
||||
handleExit();
|
||||
}
|
||||
updateLogStatus();
|
||||
if (!config.appSetting.silentLaunch) {
|
||||
window?.show();
|
||||
}
|
||||
await globalState.initCore(
|
||||
appState: appState,
|
||||
clashConfig: clashConfig,
|
||||
config: config,
|
||||
);
|
||||
await _initStatus();
|
||||
autoUpdateProfiles();
|
||||
autoCheckUpdate();
|
||||
}
|
||||
|
||||
_initStatus() async {
|
||||
if (Platform.isAndroid) {
|
||||
globalState.updateStartTime();
|
||||
}
|
||||
@@ -316,8 +346,6 @@ class AppController {
|
||||
} else {
|
||||
await updateStatus(config.appSetting.autoRun);
|
||||
}
|
||||
autoUpdateProfiles();
|
||||
autoCheckUpdate();
|
||||
}
|
||||
|
||||
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() {
|
||||
clashConfig.tun = clashConfig.tun.copyWith(
|
||||
enable: !clashConfig.tun.enable,
|
||||
@@ -547,12 +588,6 @@ class AppController {
|
||||
);
|
||||
}
|
||||
|
||||
updateAdminAutoLaunch() {
|
||||
config.appSetting = config.appSetting.copyWith(
|
||||
adminAutoLaunch: !config.appSetting.adminAutoLaunch,
|
||||
);
|
||||
}
|
||||
|
||||
updateVisible() async {
|
||||
final visible = await window?.isVisible();
|
||||
if (visible != null && !visible) {
|
||||
@@ -602,7 +637,7 @@ class AppController {
|
||||
}
|
||||
|
||||
updateTray([bool focus = false]) async {
|
||||
globalState.updateTray(
|
||||
tray.update(
|
||||
appState: appState,
|
||||
appFlowingState: appFlowingState,
|
||||
config: config,
|
||||
|
||||
@@ -178,3 +178,39 @@ enum RouteMode {
|
||||
bypassPrivate,
|
||||
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 {
|
||||
const ApplicationSettingFragment({super.key});
|
||||
|
||||
@@ -134,8 +108,6 @@ class ApplicationSettingFragment extends StatelessWidget {
|
||||
);
|
||||
},
|
||||
),
|
||||
if(Platform.isWindows)
|
||||
const AdminAutoLaunchItem(),
|
||||
if (system.isDesktop)
|
||||
Selector<Config, bool>(
|
||||
selector: (_, config) => config.appSetting.silentLaunch,
|
||||
|
||||
@@ -27,18 +27,19 @@ class _ConnectionsFragmentState extends State<ConnectionsFragment> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
connectionsNotifier.value = connectionsNotifier.value
|
||||
.copyWith(connections: clashCore.getConnections());
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
connectionsNotifier.value = connectionsNotifier.value.copyWith(
|
||||
connections: await clashCore.getConnections(),
|
||||
);
|
||||
if (timer != null) {
|
||||
timer?.cancel();
|
||||
timer = null;
|
||||
}
|
||||
timer = Timer.periodic(
|
||||
const Duration(seconds: 1),
|
||||
(timer) {
|
||||
(timer) async {
|
||||
connectionsNotifier.value = connectionsNotifier.value.copyWith(
|
||||
connections: clashCore.getConnections(),
|
||||
connections: await clashCore.getConnections(),
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -66,10 +67,10 @@ class _ConnectionsFragmentState extends State<ConnectionsFragment> {
|
||||
width: 8,
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
onPressed: () async {
|
||||
clashCore.closeConnections();
|
||||
connectionsNotifier.value = connectionsNotifier.value.copyWith(
|
||||
connections: clashCore.getConnections(),
|
||||
connections: await clashCore.getConnections(),
|
||||
);
|
||||
},
|
||||
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);
|
||||
connectionsNotifier.value = connectionsNotifier.value
|
||||
.copyWith(connections: clashCore.getConnections());
|
||||
connectionsNotifier.value = connectionsNotifier.value.copyWith(
|
||||
connections: await clashCore.getConnections(),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -239,10 +241,10 @@ class ConnectionsSearchDelegate extends SearchDelegate {
|
||||
);
|
||||
}
|
||||
|
||||
_handleBlockConnection(String id) {
|
||||
_handleBlockConnection(String id) async {
|
||||
clashCore.closeConnection(id);
|
||||
connectionsNotifier.value = connectionsNotifier.value.copyWith(
|
||||
connections: clashCore.getConnections(),
|
||||
connections: await clashCore.getConnections(),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
@@ -68,11 +67,10 @@ class _DashboardFragmentState extends State<DashboardFragment> {
|
||||
// child: const VPNSwitch(),
|
||||
// ),
|
||||
if (system.isDesktop) ...[
|
||||
if (Platform.isWindows)
|
||||
GridItem(
|
||||
crossAxisCellCount: switchCount,
|
||||
child: const TUNButton(),
|
||||
),
|
||||
GridItem(
|
||||
crossAxisCellCount: switchCount,
|
||||
child: const TUNButton(),
|
||||
),
|
||||
GridItem(
|
||||
crossAxisCellCount: switchCount,
|
||||
child: const SystemProxyButton(),
|
||||
|
||||
@@ -9,6 +9,13 @@ import 'package:fl_clash/widgets/widgets.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
final networkDetectionState = ValueNotifier<NetworkDetectionState>(
|
||||
const NetworkDetectionState(
|
||||
isTesting: true,
|
||||
ipInfo: null,
|
||||
),
|
||||
);
|
||||
|
||||
class NetworkDetection extends StatefulWidget {
|
||||
const NetworkDetection({super.key});
|
||||
|
||||
@@ -17,12 +24,6 @@ class NetworkDetection extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _NetworkDetectionState extends State<NetworkDetection> {
|
||||
final networkDetectionState = ValueNotifier<NetworkDetectionState>(
|
||||
const NetworkDetectionState(
|
||||
isTesting: true,
|
||||
ipInfo: null,
|
||||
),
|
||||
);
|
||||
bool? _preIsStart;
|
||||
Function? _checkIpDebounce;
|
||||
Timer? _setTimeoutTimer;
|
||||
@@ -55,17 +56,20 @@ class _NetworkDetectionState extends State<NetworkDetection> {
|
||||
);
|
||||
return;
|
||||
}
|
||||
_setTimeoutTimer = Timer(const Duration(milliseconds: 2000), () {
|
||||
_clearSetTimeoutTimer();
|
||||
_setTimeoutTimer = Timer(const Duration(milliseconds: 300), () {
|
||||
networkDetectionState.value = networkDetectionState.value.copyWith(
|
||||
isTesting: false,
|
||||
ipInfo: null,
|
||||
);
|
||||
});
|
||||
} catch (_) {
|
||||
networkDetectionState.value = networkDetectionState.value.copyWith(
|
||||
isTesting: true,
|
||||
ipInfo: null,
|
||||
);
|
||||
} catch (e) {
|
||||
if (e.toString() == "cancelled") {
|
||||
networkDetectionState.value = networkDetectionState.value.copyWith(
|
||||
isTesting: true,
|
||||
ipInfo: null,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,9 +96,8 @@ class _NetworkDetectionState extends State<NetworkDetection> {
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
dispose() {
|
||||
super.dispose();
|
||||
networkDetectionState.dispose();
|
||||
}
|
||||
|
||||
String countryCodeToEmoji(String countryCode) {
|
||||
@@ -156,7 +159,8 @@ class _NetworkDetectionState extends State<NetworkDetection> {
|
||||
.textTheme
|
||||
.titleLarge
|
||||
?.copyWith(
|
||||
fontFamily: FontFamily.twEmoji.value,
|
||||
fontFamily:
|
||||
FontFamily.twEmoji.value,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -115,11 +115,10 @@ class ProxyCard extends StatelessWidget {
|
||||
groupName,
|
||||
nextProxyName,
|
||||
);
|
||||
appController.changeProxy(
|
||||
groupName: groupName,
|
||||
proxyName: nextProxyName,
|
||||
);
|
||||
await appController.updateGroupDebounce();
|
||||
await appController.changeProxyDebounce([
|
||||
groupName,
|
||||
nextProxyName,
|
||||
]);
|
||||
return;
|
||||
}
|
||||
globalState.showSnackBar(
|
||||
|
||||
@@ -4,7 +4,7 @@ import 'dart:io';
|
||||
import 'package:fl_clash/clash/clash.dart';
|
||||
import 'package:fl_clash/common/common.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/widgets/widgets.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -56,7 +56,7 @@ class _ProvidersState extends State<Providers> {
|
||||
providerName: provider.name,
|
||||
);
|
||||
appState.setProvider(
|
||||
clashCore.getExternalProvider(provider.name),
|
||||
await clashCore.getExternalProvider(provider.name),
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -122,7 +122,7 @@ class ProviderItem extends StatelessWidget {
|
||||
if (message.isNotEmpty) throw message;
|
||||
});
|
||||
appState.setProvider(
|
||||
clashCore.getExternalProvider(provider.name),
|
||||
await clashCore.getExternalProvider(provider.name),
|
||||
);
|
||||
});
|
||||
await globalState.appController.updateGroupDebounce();
|
||||
@@ -143,7 +143,7 @@ class ProviderItem extends StatelessWidget {
|
||||
);
|
||||
if (message.isNotEmpty) throw message;
|
||||
appState.setProvider(
|
||||
clashCore.getExternalProvider(provider.name),
|
||||
await clashCore.getExternalProvider(provider.name),
|
||||
);
|
||||
if (message.isNotEmpty) throw message;
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
@@ -10,6 +11,8 @@ import 'package:provider/provider.dart';
|
||||
import 'card.dart';
|
||||
import 'common.dart';
|
||||
|
||||
List<Proxy> currentProxies = [];
|
||||
|
||||
typedef GroupNameKeyMap = Map<String, GlobalObjectKey<ProxyGroupViewState>>;
|
||||
|
||||
class ProxiesTabFragment extends StatefulWidget {
|
||||
@@ -28,7 +31,7 @@ class ProxiesTabFragmentState extends State<ProxiesTabFragment>
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
_tabController?.dispose();
|
||||
_destroyTabController();
|
||||
}
|
||||
|
||||
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
|
||||
Widget build(BuildContext context) {
|
||||
return Selector2<AppState, Config, ProxiesSelectorState>(
|
||||
@@ -119,8 +152,7 @@ class ProxiesTabFragmentState extends State<ProxiesTabFragment>
|
||||
},
|
||||
shouldRebuild: (prev, next) {
|
||||
if (!stringListEquality.equals(prev.groupNames, next.groupNames)) {
|
||||
_tabController?.dispose();
|
||||
_tabController = null;
|
||||
_destroyTabController();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -129,12 +161,8 @@ class ProxiesTabFragmentState extends State<ProxiesTabFragment>
|
||||
final index = state.groupNames.indexWhere(
|
||||
(item) => item == state.currentGroupName,
|
||||
);
|
||||
_tabController ??= TabController(
|
||||
length: state.groupNames.length,
|
||||
initialIndex: index == -1 ? 0 : index,
|
||||
vsync: this,
|
||||
);
|
||||
GroupNameKeyMap keyMap = {};
|
||||
_updateTabController(state.groupNames.length, index);
|
||||
final GroupNameKeyMap keyMap = {};
|
||||
final children = state.groupNames.map((groupName) {
|
||||
keyMap[groupName] = GlobalObjectKey(groupName);
|
||||
return KeepScope(
|
||||
@@ -167,16 +195,6 @@ class ProxiesTabFragmentState extends State<ProxiesTabFragment>
|
||||
left: 16,
|
||||
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,
|
||||
isScrollable: true,
|
||||
tabAlignment: TabAlignment.start,
|
||||
@@ -243,14 +261,13 @@ class ProxyGroupView extends StatefulWidget {
|
||||
class ProxyGroupViewState extends State<ProxyGroupView> {
|
||||
var isLock = false;
|
||||
final _controller = ScrollController();
|
||||
List<Proxy> _lastProxies = [];
|
||||
|
||||
String get groupName => widget.groupName;
|
||||
|
||||
_delayTest(List<Proxy> proxies) async {
|
||||
_delayTest() async {
|
||||
if (isLock) return;
|
||||
isLock = true;
|
||||
await delayTest(proxies);
|
||||
await delayTest(currentProxies);
|
||||
isLock = false;
|
||||
}
|
||||
|
||||
@@ -269,7 +286,7 @@ class ProxyGroupViewState extends State<ProxyGroupView> {
|
||||
16 +
|
||||
getScrollToSelectedOffset(
|
||||
groupName: groupName,
|
||||
proxies: _lastProxies,
|
||||
proxies: currentProxies,
|
||||
),
|
||||
_controller.position.maxScrollExtent,
|
||||
),
|
||||
@@ -278,7 +295,7 @@ class ProxyGroupViewState extends State<ProxyGroupView> {
|
||||
);
|
||||
}
|
||||
|
||||
initFab(bool isCurrent, List<Proxy> proxies) {
|
||||
initFab(bool isCurrent) {
|
||||
if (!isCurrent) {
|
||||
return;
|
||||
}
|
||||
@@ -287,9 +304,7 @@ class ProxyGroupViewState extends State<ProxyGroupView> {
|
||||
context.findAncestorStateOfType<CommonScaffoldState>();
|
||||
commonScaffoldState?.floatingActionButton = DelayTestButton(
|
||||
onClick: () async {
|
||||
await _delayTest(
|
||||
proxies,
|
||||
);
|
||||
await _delayTest();
|
||||
},
|
||||
);
|
||||
});
|
||||
@@ -319,11 +334,10 @@ class ProxyGroupViewState extends State<ProxyGroupView> {
|
||||
final sortedProxies = globalState.appController.getSortProxies(
|
||||
proxies,
|
||||
);
|
||||
_lastProxies = sortedProxies;
|
||||
return ActiveBuilder(
|
||||
label: "proxies",
|
||||
builder: (isCurrent, child) {
|
||||
initFab(isCurrent, proxies);
|
||||
initFab(isCurrent);
|
||||
return child!;
|
||||
},
|
||||
child: Align(
|
||||
@@ -381,7 +395,9 @@ class _DelayTestButtonState extends State<DelayTestButton>
|
||||
_healthcheck() async {
|
||||
_controller.forward();
|
||||
await widget.onClick();
|
||||
_controller.reverse();
|
||||
if (mounted) {
|
||||
_controller.reverse();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -330,5 +330,7 @@
|
||||
"routeMode_bypassPrivate": "Bypass private route address",
|
||||
"routeMode_config": "Use config",
|
||||
"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_config": "使用配置",
|
||||
"routeAddress": "路由地址",
|
||||
"routeAddressDesc": "配置监听路由地址"
|
||||
"routeAddressDesc": "配置监听路由地址",
|
||||
"pleaseInputAdminPassword": "请输入管理员密码",
|
||||
"copyEnvVar": "复制环境变量"
|
||||
}
|
||||
|
||||
@@ -121,6 +121,8 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"View current connections data"),
|
||||
"connectivity": MessageLookupByLibrary.simpleMessage("Connectivity:"),
|
||||
"copy": MessageLookupByLibrary.simpleMessage("Copy"),
|
||||
"copyEnvVar": MessageLookupByLibrary.simpleMessage(
|
||||
"Copying environment variables"),
|
||||
"core": MessageLookupByLibrary.simpleMessage("Core"),
|
||||
"coreInfo": MessageLookupByLibrary.simpleMessage("Core info"),
|
||||
"country": MessageLookupByLibrary.simpleMessage("Country"),
|
||||
@@ -326,6 +328,8 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"paste": MessageLookupByLibrary.simpleMessage("Paste"),
|
||||
"pleaseBindWebDAV":
|
||||
MessageLookupByLibrary.simpleMessage("Please bind WebDAV"),
|
||||
"pleaseInputAdminPassword": MessageLookupByLibrary.simpleMessage(
|
||||
"Please enter the admin password"),
|
||||
"pleaseUploadFile":
|
||||
MessageLookupByLibrary.simpleMessage("Please upload file"),
|
||||
"pleaseUploadValidQrcode": MessageLookupByLibrary.simpleMessage(
|
||||
|
||||
@@ -98,6 +98,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"connectionsDesc": MessageLookupByLibrary.simpleMessage("查看当前连接数据"),
|
||||
"connectivity": MessageLookupByLibrary.simpleMessage("连通性:"),
|
||||
"copy": MessageLookupByLibrary.simpleMessage("复制"),
|
||||
"copyEnvVar": MessageLookupByLibrary.simpleMessage("复制环境变量"),
|
||||
"core": MessageLookupByLibrary.simpleMessage("内核"),
|
||||
"coreInfo": MessageLookupByLibrary.simpleMessage("内核信息"),
|
||||
"country": MessageLookupByLibrary.simpleMessage("区域"),
|
||||
@@ -259,6 +260,8 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"passwordTip": MessageLookupByLibrary.simpleMessage("密码不能为空"),
|
||||
"paste": MessageLookupByLibrary.simpleMessage("粘贴"),
|
||||
"pleaseBindWebDAV": MessageLookupByLibrary.simpleMessage("请绑定WebDAV"),
|
||||
"pleaseInputAdminPassword":
|
||||
MessageLookupByLibrary.simpleMessage("请输入管理员密码"),
|
||||
"pleaseUploadFile": MessageLookupByLibrary.simpleMessage("请上传文件"),
|
||||
"pleaseUploadValidQrcode":
|
||||
MessageLookupByLibrary.simpleMessage("请上传有效的二维码"),
|
||||
|
||||
@@ -3369,6 +3369,26 @@ class AppLocalizations {
|
||||
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> {
|
||||
|
||||
@@ -8,14 +8,15 @@ import 'package:fl_clash/plugins/vpn.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
|
||||
import 'application.dart';
|
||||
import 'common/common.dart';
|
||||
import 'l10n/l10n.dart';
|
||||
import 'models/models.dart';
|
||||
import 'common/common.dart';
|
||||
|
||||
Future<void> main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
clashCore.initMessage();
|
||||
clashLib?.initMessage();
|
||||
globalState.packageInfo = await PackageInfo.fromPlatform();
|
||||
final version = await system.version;
|
||||
final config = await preferences.getConfig() ?? Config();
|
||||
@@ -36,17 +37,12 @@ Future<void> main() async {
|
||||
openLogs: config.appSetting.openLogs,
|
||||
hasProxies: false,
|
||||
);
|
||||
globalState.updateTray(
|
||||
tray.update(
|
||||
appState: appState,
|
||||
appFlowingState: appFlowingState,
|
||||
config: config,
|
||||
clashConfig: clashConfig,
|
||||
);
|
||||
await globalState.init(
|
||||
appState: appState,
|
||||
config: config,
|
||||
clashConfig: clashConfig,
|
||||
);
|
||||
HttpOverrides.global = FlClashHttpOverrides();
|
||||
runAppWithPreferences(
|
||||
const Application(),
|
||||
@@ -69,39 +65,66 @@ Future<void> vpnService() async {
|
||||
other.getLocaleForString(config.appSetting.locale) ??
|
||||
WidgetsBinding.instance.platformDispatcher.locale,
|
||||
);
|
||||
|
||||
final appState = AppState(
|
||||
mode: clashConfig.mode,
|
||||
selectedMap: config.currentSelectedMap,
|
||||
version: version,
|
||||
);
|
||||
|
||||
await globalState.init(
|
||||
appState: appState,
|
||||
config: config,
|
||||
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(
|
||||
ServiceMessageHandler(
|
||||
onProtect: (Fd fd) async {
|
||||
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);
|
||||
clashCore.setProcessMap(
|
||||
clashLib?.setProcessMap(
|
||||
ProcessMapItem(
|
||||
id: process.id,
|
||||
value: packageName ?? "",
|
||||
),
|
||||
);
|
||||
},
|
||||
onStarted: (String runTime) async {
|
||||
await globalState.applyProfile(
|
||||
appState: appState,
|
||||
config: config,
|
||||
clashConfig: clashConfig,
|
||||
);
|
||||
},
|
||||
onLoaded: (String groupName) {
|
||||
final currentSelectedMap = config.currentSelectedMap;
|
||||
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
|
||||
class ServiceMessageHandler with ServiceMessageListener {
|
||||
final Function(Fd fd) _onProtect;
|
||||
final Function(Process process) _onProcess;
|
||||
final Function(String runTime) _onStarted;
|
||||
final Function(ProcessData process) _onProcess;
|
||||
final Function(String providerName) _onLoaded;
|
||||
|
||||
const ServiceMessageHandler({
|
||||
required Function(Fd fd) onProtect,
|
||||
required Function(Process process) onProcess,
|
||||
required Function(String runTime) onStarted,
|
||||
required Function(ProcessData process) onProcess,
|
||||
required Function(String providerName) onLoaded,
|
||||
}) : _onProtect = onProtect,
|
||||
_onProcess = onProcess,
|
||||
_onStarted = onStarted,
|
||||
_onLoaded = onLoaded;
|
||||
|
||||
@override
|
||||
@@ -159,15 +159,10 @@ class ServiceMessageHandler with ServiceMessageListener {
|
||||
}
|
||||
|
||||
@override
|
||||
onProcess(Process process) {
|
||||
onProcess(ProcessData process) {
|
||||
_onProcess(process);
|
||||
}
|
||||
|
||||
@override
|
||||
onStarted(String runTime) {
|
||||
_onStarted(runTime);
|
||||
}
|
||||
|
||||
@override
|
||||
onLoaded(String 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/plugins/app.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -23,6 +24,28 @@ class _AndroidContainerState extends State<AndroidManager> {
|
||||
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) {
|
||||
return Selector<Config, bool>(
|
||||
selector: (_, config) => config.appSetting.hidden,
|
||||
@@ -36,6 +59,10 @@ class _AndroidContainerState extends State<AndroidManager> {
|
||||
|
||||
@override
|
||||
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) {
|
||||
return Selector<Config, String?>(
|
||||
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) {
|
||||
_changeProfile();
|
||||
return child!;
|
||||
},
|
||||
child: child,
|
||||
@@ -101,10 +80,8 @@ class _ClashContainerState extends State<ClashManager> with AppMessageListener {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return _changeProfileContainer(
|
||||
_updateCoreState(
|
||||
_updateContainer(
|
||||
widget.child,
|
||||
),
|
||||
_updateContainer(
|
||||
widget.child,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -158,7 +135,7 @@ class _ClashContainerState extends State<ClashManager> with AppMessageListener {
|
||||
Future<void> onLoaded(String providerName) async {
|
||||
final appController = globalState.appController;
|
||||
appController.appState.setProvider(
|
||||
clashCore.getExternalProvider(
|
||||
await clashCore.getExternalProvider(
|
||||
providerName,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -30,13 +30,15 @@ class _TrayContainerState extends State<TrayManager> with TrayListener {
|
||||
selector: (_, appState, appFlowingState, config, clashConfig) =>
|
||||
TrayState(
|
||||
mode: clashConfig.mode,
|
||||
adminAutoLaunch: config.appSetting.adminAutoLaunch,
|
||||
autoLaunch: config.appSetting.autoLaunch,
|
||||
isStart: appFlowingState.isStart,
|
||||
locale: config.appSetting.locale,
|
||||
systemProxy: config.networkProps.systemProxy,
|
||||
tunEnable: clashConfig.tun.enable,
|
||||
brightness: appState.brightness,
|
||||
port: clashConfig.mixedPort,
|
||||
groups: appState.groups,
|
||||
map: appState.selectedMap,
|
||||
),
|
||||
shouldRebuild: (prev, next) {
|
||||
if (prev != next) {
|
||||
|
||||
@@ -28,7 +28,6 @@ class _WindowContainerState extends State<WindowManager>
|
||||
return Selector<Config, AutoLaunchState>(
|
||||
selector: (_, config) => AutoLaunchState(
|
||||
isAutoLaunch: config.appSetting.autoLaunch,
|
||||
isAdminAutoLaunch: config.appSetting.adminAutoLaunch,
|
||||
),
|
||||
builder: (_, state, child) {
|
||||
updateLaunchDebounce ??= debounce((AutoLaunchState state) {
|
||||
@@ -88,8 +87,9 @@ class _WindowContainerState extends State<WindowManager>
|
||||
}
|
||||
|
||||
@override
|
||||
void onTaskbarCreated() {
|
||||
Future<void> onTaskbarCreated() async {
|
||||
globalState.appController.updateTray(true);
|
||||
await globalState.appController.restartCore();
|
||||
super.onTaskbarCreated();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'common.dart';
|
||||
import 'ffi.dart';
|
||||
import 'core.dart';
|
||||
import 'profile.dart';
|
||||
|
||||
typedef DelayMap = Map<String, int?>;
|
||||
|
||||
@@ -482,6 +482,28 @@ class ClashConfig extends ChangeNotifier {
|
||||
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() {
|
||||
return _$ClashConfigToJson(this);
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ class AppSetting with _$AppSetting {
|
||||
String? locale,
|
||||
@Default(false) bool onlyProxy,
|
||||
@Default(false) bool autoLaunch,
|
||||
@Default(false) bool adminAutoLaunch,
|
||||
@Default(false) bool silentLaunch,
|
||||
@Default(false) bool autoRun,
|
||||
@Default(false) bool openLogs,
|
||||
|
||||
@@ -1,11 +1,35 @@
|
||||
// ignore_for_file: invalid_annotation_target
|
||||
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'generated/ffi.freezed.dart';
|
||||
part 'generated/ffi.g.dart';
|
||||
part 'generated/core.freezed.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
|
||||
class CoreState with _$CoreState {
|
||||
@@ -124,14 +148,14 @@ class Now with _$Now {
|
||||
}
|
||||
|
||||
@freezed
|
||||
class Process with _$Process {
|
||||
const factory Process({
|
||||
class ProcessData with _$ProcessData {
|
||||
const factory ProcessData({
|
||||
required int id,
|
||||
required Metadata metadata,
|
||||
}) = _Process;
|
||||
}) = _ProcessData;
|
||||
|
||||
factory Process.fromJson(Map<String, Object?> json) =>
|
||||
_$ProcessFromJson(json);
|
||||
factory ProcessData.fromJson(Map<String, Object?> json) =>
|
||||
_$ProcessDataFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
@@ -212,24 +236,19 @@ class TunProps with _$TunProps {
|
||||
_$TunPropsFromJson(json);
|
||||
}
|
||||
|
||||
abstract mixin class AppMessageListener {
|
||||
void onLog(Log log) {}
|
||||
@freezed
|
||||
class Action with _$Action {
|
||||
const factory Action({
|
||||
required ActionMethod method,
|
||||
required dynamic data,
|
||||
required String id,
|
||||
}) = _Action;
|
||||
|
||||
void onDelay(Delay delay) {}
|
||||
|
||||
void onRequest(Connection connection) {}
|
||||
|
||||
void onStarted(String runTime) {}
|
||||
|
||||
void onLoaded(String providerName) {}
|
||||
factory Action.fromJson(Map<String, Object?> json) => _$ActionFromJson(json);
|
||||
}
|
||||
|
||||
abstract mixin class ServiceMessageListener {
|
||||
onProtect(Fd fd) {}
|
||||
|
||||
onProcess(Process process) {}
|
||||
|
||||
onStarted(String runTime) {}
|
||||
|
||||
onLoaded(String providerName) {}
|
||||
extension ActionExt on Action {
|
||||
String get toJson {
|
||||
return json.encode(this);
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,6 @@ mixin _$AppSetting {
|
||||
String? get locale => throw _privateConstructorUsedError;
|
||||
bool get onlyProxy => throw _privateConstructorUsedError;
|
||||
bool get autoLaunch => throw _privateConstructorUsedError;
|
||||
bool get adminAutoLaunch => throw _privateConstructorUsedError;
|
||||
bool get silentLaunch => throw _privateConstructorUsedError;
|
||||
bool get autoRun => throw _privateConstructorUsedError;
|
||||
bool get openLogs => throw _privateConstructorUsedError;
|
||||
@@ -56,7 +55,6 @@ abstract class $AppSettingCopyWith<$Res> {
|
||||
{String? locale,
|
||||
bool onlyProxy,
|
||||
bool autoLaunch,
|
||||
bool adminAutoLaunch,
|
||||
bool silentLaunch,
|
||||
bool autoRun,
|
||||
bool openLogs,
|
||||
@@ -88,7 +86,6 @@ class _$AppSettingCopyWithImpl<$Res, $Val extends AppSetting>
|
||||
Object? locale = freezed,
|
||||
Object? onlyProxy = null,
|
||||
Object? autoLaunch = null,
|
||||
Object? adminAutoLaunch = null,
|
||||
Object? silentLaunch = null,
|
||||
Object? autoRun = null,
|
||||
Object? openLogs = null,
|
||||
@@ -114,10 +111,6 @@ class _$AppSettingCopyWithImpl<$Res, $Val extends AppSetting>
|
||||
? _value.autoLaunch
|
||||
: autoLaunch // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
adminAutoLaunch: null == adminAutoLaunch
|
||||
? _value.adminAutoLaunch
|
||||
: adminAutoLaunch // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
silentLaunch: null == silentLaunch
|
||||
? _value.silentLaunch
|
||||
: silentLaunch // ignore: cast_nullable_to_non_nullable
|
||||
@@ -178,7 +171,6 @@ abstract class _$$AppSettingImplCopyWith<$Res>
|
||||
{String? locale,
|
||||
bool onlyProxy,
|
||||
bool autoLaunch,
|
||||
bool adminAutoLaunch,
|
||||
bool silentLaunch,
|
||||
bool autoRun,
|
||||
bool openLogs,
|
||||
@@ -208,7 +200,6 @@ class __$$AppSettingImplCopyWithImpl<$Res>
|
||||
Object? locale = freezed,
|
||||
Object? onlyProxy = null,
|
||||
Object? autoLaunch = null,
|
||||
Object? adminAutoLaunch = null,
|
||||
Object? silentLaunch = null,
|
||||
Object? autoRun = null,
|
||||
Object? openLogs = null,
|
||||
@@ -234,10 +225,6 @@ class __$$AppSettingImplCopyWithImpl<$Res>
|
||||
? _value.autoLaunch
|
||||
: autoLaunch // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
adminAutoLaunch: null == adminAutoLaunch
|
||||
? _value.adminAutoLaunch
|
||||
: adminAutoLaunch // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
silentLaunch: null == silentLaunch
|
||||
? _value.silentLaunch
|
||||
: silentLaunch // ignore: cast_nullable_to_non_nullable
|
||||
@@ -293,7 +280,6 @@ class _$AppSettingImpl implements _AppSetting {
|
||||
{this.locale,
|
||||
this.onlyProxy = false,
|
||||
this.autoLaunch = false,
|
||||
this.adminAutoLaunch = false,
|
||||
this.silentLaunch = false,
|
||||
this.autoRun = false,
|
||||
this.openLogs = false,
|
||||
@@ -319,9 +305,6 @@ class _$AppSettingImpl implements _AppSetting {
|
||||
final bool autoLaunch;
|
||||
@override
|
||||
@JsonKey()
|
||||
final bool adminAutoLaunch;
|
||||
@override
|
||||
@JsonKey()
|
||||
final bool silentLaunch;
|
||||
@override
|
||||
@JsonKey()
|
||||
@@ -356,7 +339,7 @@ class _$AppSettingImpl implements _AppSetting {
|
||||
|
||||
@override
|
||||
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
|
||||
@@ -369,8 +352,6 @@ class _$AppSettingImpl implements _AppSetting {
|
||||
other.onlyProxy == onlyProxy) &&
|
||||
(identical(other.autoLaunch, autoLaunch) ||
|
||||
other.autoLaunch == autoLaunch) &&
|
||||
(identical(other.adminAutoLaunch, adminAutoLaunch) ||
|
||||
other.adminAutoLaunch == adminAutoLaunch) &&
|
||||
(identical(other.silentLaunch, silentLaunch) ||
|
||||
other.silentLaunch == silentLaunch) &&
|
||||
(identical(other.autoRun, autoRun) || other.autoRun == autoRun) &&
|
||||
@@ -399,7 +380,6 @@ class _$AppSettingImpl implements _AppSetting {
|
||||
locale,
|
||||
onlyProxy,
|
||||
autoLaunch,
|
||||
adminAutoLaunch,
|
||||
silentLaunch,
|
||||
autoRun,
|
||||
openLogs,
|
||||
@@ -433,7 +413,6 @@ abstract class _AppSetting implements AppSetting {
|
||||
{final String? locale,
|
||||
final bool onlyProxy,
|
||||
final bool autoLaunch,
|
||||
final bool adminAutoLaunch,
|
||||
final bool silentLaunch,
|
||||
final bool autoRun,
|
||||
final bool openLogs,
|
||||
@@ -456,8 +435,6 @@ abstract class _AppSetting implements AppSetting {
|
||||
@override
|
||||
bool get autoLaunch;
|
||||
@override
|
||||
bool get adminAutoLaunch;
|
||||
@override
|
||||
bool get silentLaunch;
|
||||
@override
|
||||
bool get autoRun;
|
||||
|
||||
@@ -56,7 +56,6 @@ _$AppSettingImpl _$$AppSettingImplFromJson(Map<String, dynamic> json) =>
|
||||
locale: json['locale'] as String?,
|
||||
onlyProxy: json['onlyProxy'] as bool? ?? false,
|
||||
autoLaunch: json['autoLaunch'] as bool? ?? false,
|
||||
adminAutoLaunch: json['adminAutoLaunch'] as bool? ?? false,
|
||||
silentLaunch: json['silentLaunch'] as bool? ?? false,
|
||||
autoRun: json['autoRun'] as bool? ?? false,
|
||||
openLogs: json['openLogs'] as bool? ?? false,
|
||||
@@ -75,7 +74,6 @@ Map<String, dynamic> _$$AppSettingImplToJson(_$AppSettingImpl instance) =>
|
||||
'locale': instance.locale,
|
||||
'onlyProxy': instance.onlyProxy,
|
||||
'autoLaunch': instance.autoLaunch,
|
||||
'adminAutoLaunch': instance.adminAutoLaunch,
|
||||
'silentLaunch': instance.silentLaunch,
|
||||
'autoRun': instance.autoRun,
|
||||
'openLogs': instance.openLogs,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// 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
|
||||
|
||||
part of '../ffi.dart';
|
||||
part of '../core.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
@@ -2080,28 +2080,30 @@ abstract class _Now implements Now {
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
Process _$ProcessFromJson(Map<String, dynamic> json) {
|
||||
return _Process.fromJson(json);
|
||||
ProcessData _$ProcessDataFromJson(Map<String, dynamic> json) {
|
||||
return _ProcessData.fromJson(json);
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$Process {
|
||||
mixin _$ProcessData {
|
||||
int get id => 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;
|
||||
|
||||
/// Create a copy of Process
|
||||
/// Create a copy of ProcessData
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
$ProcessCopyWith<Process> get copyWith => throw _privateConstructorUsedError;
|
||||
$ProcessDataCopyWith<ProcessData> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $ProcessCopyWith<$Res> {
|
||||
factory $ProcessCopyWith(Process value, $Res Function(Process) then) =
|
||||
_$ProcessCopyWithImpl<$Res, Process>;
|
||||
abstract class $ProcessDataCopyWith<$Res> {
|
||||
factory $ProcessDataCopyWith(
|
||||
ProcessData value, $Res Function(ProcessData) then) =
|
||||
_$ProcessDataCopyWithImpl<$Res, ProcessData>;
|
||||
@useResult
|
||||
$Res call({int id, Metadata metadata});
|
||||
|
||||
@@ -2109,16 +2111,16 @@ abstract class $ProcessCopyWith<$Res> {
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$ProcessCopyWithImpl<$Res, $Val extends Process>
|
||||
implements $ProcessCopyWith<$Res> {
|
||||
_$ProcessCopyWithImpl(this._value, this._then);
|
||||
class _$ProcessDataCopyWithImpl<$Res, $Val extends ProcessData>
|
||||
implements $ProcessDataCopyWith<$Res> {
|
||||
_$ProcessDataCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
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.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
@@ -2138,7 +2140,7 @@ class _$ProcessCopyWithImpl<$Res, $Val extends Process>
|
||||
) as $Val);
|
||||
}
|
||||
|
||||
/// Create a copy of Process
|
||||
/// Create a copy of ProcessData
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
@@ -2150,10 +2152,11 @@ class _$ProcessCopyWithImpl<$Res, $Val extends Process>
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$ProcessImplCopyWith<$Res> implements $ProcessCopyWith<$Res> {
|
||||
factory _$$ProcessImplCopyWith(
|
||||
_$ProcessImpl value, $Res Function(_$ProcessImpl) then) =
|
||||
__$$ProcessImplCopyWithImpl<$Res>;
|
||||
abstract class _$$ProcessDataImplCopyWith<$Res>
|
||||
implements $ProcessDataCopyWith<$Res> {
|
||||
factory _$$ProcessDataImplCopyWith(
|
||||
_$ProcessDataImpl value, $Res Function(_$ProcessDataImpl) then) =
|
||||
__$$ProcessDataImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({int id, Metadata metadata});
|
||||
@@ -2163,14 +2166,14 @@ abstract class _$$ProcessImplCopyWith<$Res> implements $ProcessCopyWith<$Res> {
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$ProcessImplCopyWithImpl<$Res>
|
||||
extends _$ProcessCopyWithImpl<$Res, _$ProcessImpl>
|
||||
implements _$$ProcessImplCopyWith<$Res> {
|
||||
__$$ProcessImplCopyWithImpl(
|
||||
_$ProcessImpl _value, $Res Function(_$ProcessImpl) _then)
|
||||
class __$$ProcessDataImplCopyWithImpl<$Res>
|
||||
extends _$ProcessDataCopyWithImpl<$Res, _$ProcessDataImpl>
|
||||
implements _$$ProcessDataImplCopyWith<$Res> {
|
||||
__$$ProcessDataImplCopyWithImpl(
|
||||
_$ProcessDataImpl _value, $Res Function(_$ProcessDataImpl) _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.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
@@ -2178,7 +2181,7 @@ class __$$ProcessImplCopyWithImpl<$Res>
|
||||
Object? id = null,
|
||||
Object? metadata = null,
|
||||
}) {
|
||||
return _then(_$ProcessImpl(
|
||||
return _then(_$ProcessDataImpl(
|
||||
id: null == id
|
||||
? _value.id
|
||||
: id // ignore: cast_nullable_to_non_nullable
|
||||
@@ -2193,11 +2196,11 @@ class __$$ProcessImplCopyWithImpl<$Res>
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$ProcessImpl implements _Process {
|
||||
const _$ProcessImpl({required this.id, required this.metadata});
|
||||
class _$ProcessDataImpl implements _ProcessData {
|
||||
const _$ProcessDataImpl({required this.id, required this.metadata});
|
||||
|
||||
factory _$ProcessImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$ProcessImplFromJson(json);
|
||||
factory _$ProcessDataImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$ProcessDataImplFromJson(json);
|
||||
|
||||
@override
|
||||
final int id;
|
||||
@@ -2206,14 +2209,14 @@ class _$ProcessImpl implements _Process {
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'Process(id: $id, metadata: $metadata)';
|
||||
return 'ProcessData(id: $id, metadata: $metadata)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$ProcessImpl &&
|
||||
other is _$ProcessDataImpl &&
|
||||
(identical(other.id, id) || other.id == id) &&
|
||||
(identical(other.metadata, metadata) ||
|
||||
other.metadata == metadata));
|
||||
@@ -2223,39 +2226,40 @@ class _$ProcessImpl implements _Process {
|
||||
@override
|
||||
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.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$ProcessImplCopyWith<_$ProcessImpl> get copyWith =>
|
||||
__$$ProcessImplCopyWithImpl<_$ProcessImpl>(this, _$identity);
|
||||
_$$ProcessDataImplCopyWith<_$ProcessDataImpl> get copyWith =>
|
||||
__$$ProcessDataImplCopyWithImpl<_$ProcessDataImpl>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$$ProcessImplToJson(
|
||||
return _$$ProcessDataImplToJson(
|
||||
this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _Process implements Process {
|
||||
const factory _Process(
|
||||
abstract class _ProcessData implements ProcessData {
|
||||
const factory _ProcessData(
|
||||
{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
|
||||
int get id;
|
||||
@override
|
||||
Metadata get metadata;
|
||||
|
||||
/// Create a copy of Process
|
||||
/// Create a copy of ProcessData
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$ProcessImplCopyWith<_$ProcessImpl> get copyWith =>
|
||||
_$$ProcessDataImplCopyWith<_$ProcessDataImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
@@ -3424,3 +3428,185 @@ abstract class _TunProps implements TunProps {
|
||||
_$$TunPropsImplCopyWith<_$TunPropsImpl> get copyWith =>
|
||||
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
|
||||
|
||||
part of '../ffi.dart';
|
||||
part of '../core.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
@@ -188,13 +188,13 @@ Map<String, dynamic> _$$NowImplToJson(_$NowImpl instance) => <String, dynamic>{
|
||||
'value': instance.value,
|
||||
};
|
||||
|
||||
_$ProcessImpl _$$ProcessImplFromJson(Map<String, dynamic> json) =>
|
||||
_$ProcessImpl(
|
||||
_$ProcessDataImpl _$$ProcessDataImplFromJson(Map<String, dynamic> json) =>
|
||||
_$ProcessDataImpl(
|
||||
id: (json['id'] as num).toInt(),
|
||||
metadata: Metadata.fromJson(json['metadata'] as Map<String, dynamic>),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$ProcessImplToJson(_$ProcessImpl instance) =>
|
||||
Map<String, dynamic> _$$ProcessDataImplToJson(_$ProcessDataImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'id': instance.id,
|
||||
'metadata': instance.metadata,
|
||||
@@ -289,3 +289,44 @@ Map<String, dynamic> _$$TunPropsImplToJson(_$TunPropsImpl instance) =>
|
||||
'dns': instance.dns,
|
||||
'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
|
||||
mixin _$TrayState {
|
||||
Mode get mode => throw _privateConstructorUsedError;
|
||||
int get port => throw _privateConstructorUsedError;
|
||||
bool get autoLaunch => throw _privateConstructorUsedError;
|
||||
bool get adminAutoLaunch => throw _privateConstructorUsedError;
|
||||
bool get systemProxy => throw _privateConstructorUsedError;
|
||||
bool get tunEnable => throw _privateConstructorUsedError;
|
||||
bool get isStart => throw _privateConstructorUsedError;
|
||||
String? get locale => 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
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@@ -1068,13 +1070,15 @@ abstract class $TrayStateCopyWith<$Res> {
|
||||
@useResult
|
||||
$Res call(
|
||||
{Mode mode,
|
||||
int port,
|
||||
bool autoLaunch,
|
||||
bool adminAutoLaunch,
|
||||
bool systemProxy,
|
||||
bool tunEnable,
|
||||
bool isStart,
|
||||
String? locale,
|
||||
Brightness? brightness});
|
||||
Brightness? brightness,
|
||||
List<Group> groups,
|
||||
Map<String, String> map});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@@ -1093,27 +1097,29 @@ class _$TrayStateCopyWithImpl<$Res, $Val extends TrayState>
|
||||
@override
|
||||
$Res call({
|
||||
Object? mode = null,
|
||||
Object? port = null,
|
||||
Object? autoLaunch = null,
|
||||
Object? adminAutoLaunch = null,
|
||||
Object? systemProxy = null,
|
||||
Object? tunEnable = null,
|
||||
Object? isStart = null,
|
||||
Object? locale = freezed,
|
||||
Object? brightness = freezed,
|
||||
Object? groups = null,
|
||||
Object? map = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
mode: null == mode
|
||||
? _value.mode
|
||||
: mode // ignore: cast_nullable_to_non_nullable
|
||||
as Mode,
|
||||
port: null == port
|
||||
? _value.port
|
||||
: port // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
autoLaunch: null == autoLaunch
|
||||
? _value.autoLaunch
|
||||
: autoLaunch // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
adminAutoLaunch: null == adminAutoLaunch
|
||||
? _value.adminAutoLaunch
|
||||
: adminAutoLaunch // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
systemProxy: null == systemProxy
|
||||
? _value.systemProxy
|
||||
: systemProxy // ignore: cast_nullable_to_non_nullable
|
||||
@@ -1134,6 +1140,14 @@ class _$TrayStateCopyWithImpl<$Res, $Val extends TrayState>
|
||||
? _value.brightness
|
||||
: brightness // ignore: cast_nullable_to_non_nullable
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1148,13 +1162,15 @@ abstract class _$$TrayStateImplCopyWith<$Res>
|
||||
@useResult
|
||||
$Res call(
|
||||
{Mode mode,
|
||||
int port,
|
||||
bool autoLaunch,
|
||||
bool adminAutoLaunch,
|
||||
bool systemProxy,
|
||||
bool tunEnable,
|
||||
bool isStart,
|
||||
String? locale,
|
||||
Brightness? brightness});
|
||||
Brightness? brightness,
|
||||
List<Group> groups,
|
||||
Map<String, String> map});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@@ -1171,27 +1187,29 @@ class __$$TrayStateImplCopyWithImpl<$Res>
|
||||
@override
|
||||
$Res call({
|
||||
Object? mode = null,
|
||||
Object? port = null,
|
||||
Object? autoLaunch = null,
|
||||
Object? adminAutoLaunch = null,
|
||||
Object? systemProxy = null,
|
||||
Object? tunEnable = null,
|
||||
Object? isStart = null,
|
||||
Object? locale = freezed,
|
||||
Object? brightness = freezed,
|
||||
Object? groups = null,
|
||||
Object? map = null,
|
||||
}) {
|
||||
return _then(_$TrayStateImpl(
|
||||
mode: null == mode
|
||||
? _value.mode
|
||||
: mode // ignore: cast_nullable_to_non_nullable
|
||||
as Mode,
|
||||
port: null == port
|
||||
? _value.port
|
||||
: port // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
autoLaunch: null == autoLaunch
|
||||
? _value.autoLaunch
|
||||
: autoLaunch // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
adminAutoLaunch: null == adminAutoLaunch
|
||||
? _value.adminAutoLaunch
|
||||
: adminAutoLaunch // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
systemProxy: null == systemProxy
|
||||
? _value.systemProxy
|
||||
: systemProxy // ignore: cast_nullable_to_non_nullable
|
||||
@@ -1212,6 +1230,14 @@ class __$$TrayStateImplCopyWithImpl<$Res>
|
||||
? _value.brightness
|
||||
: brightness // ignore: cast_nullable_to_non_nullable
|
||||
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 {
|
||||
const _$TrayStateImpl(
|
||||
{required this.mode,
|
||||
required this.port,
|
||||
required this.autoLaunch,
|
||||
required this.adminAutoLaunch,
|
||||
required this.systemProxy,
|
||||
required this.tunEnable,
|
||||
required this.isStart,
|
||||
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
|
||||
final Mode mode;
|
||||
@override
|
||||
final bool autoLaunch;
|
||||
final int port;
|
||||
@override
|
||||
final bool adminAutoLaunch;
|
||||
final bool autoLaunch;
|
||||
@override
|
||||
final bool systemProxy;
|
||||
@override
|
||||
@@ -1245,10 +1275,25 @@ class _$TrayStateImpl implements _TrayState {
|
||||
final String? locale;
|
||||
@override
|
||||
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
|
||||
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
|
||||
@@ -1257,10 +1302,9 @@ class _$TrayStateImpl implements _TrayState {
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$TrayStateImpl &&
|
||||
(identical(other.mode, mode) || other.mode == mode) &&
|
||||
(identical(other.port, port) || other.port == port) &&
|
||||
(identical(other.autoLaunch, autoLaunch) ||
|
||||
other.autoLaunch == autoLaunch) &&
|
||||
(identical(other.adminAutoLaunch, adminAutoLaunch) ||
|
||||
other.adminAutoLaunch == adminAutoLaunch) &&
|
||||
(identical(other.systemProxy, systemProxy) ||
|
||||
other.systemProxy == systemProxy) &&
|
||||
(identical(other.tunEnable, tunEnable) ||
|
||||
@@ -1268,12 +1312,24 @@ class _$TrayStateImpl implements _TrayState {
|
||||
(identical(other.isStart, isStart) || other.isStart == isStart) &&
|
||||
(identical(other.locale, locale) || other.locale == locale) &&
|
||||
(identical(other.brightness, brightness) ||
|
||||
other.brightness == brightness));
|
||||
other.brightness == brightness) &&
|
||||
const DeepCollectionEquality().equals(other._groups, _groups) &&
|
||||
const DeepCollectionEquality().equals(other._map, _map));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, mode, autoLaunch,
|
||||
adminAutoLaunch, systemProxy, tunEnable, isStart, locale, brightness);
|
||||
int get hashCode => Object.hash(
|
||||
runtimeType,
|
||||
mode,
|
||||
port,
|
||||
autoLaunch,
|
||||
systemProxy,
|
||||
tunEnable,
|
||||
isStart,
|
||||
locale,
|
||||
brightness,
|
||||
const DeepCollectionEquality().hash(_groups),
|
||||
const DeepCollectionEquality().hash(_map));
|
||||
|
||||
/// Create a copy of TrayState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@@ -1287,20 +1343,22 @@ class _$TrayStateImpl implements _TrayState {
|
||||
abstract class _TrayState implements TrayState {
|
||||
const factory _TrayState(
|
||||
{required final Mode mode,
|
||||
required final int port,
|
||||
required final bool autoLaunch,
|
||||
required final bool adminAutoLaunch,
|
||||
required final bool systemProxy,
|
||||
required final bool tunEnable,
|
||||
required final bool isStart,
|
||||
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
|
||||
Mode get mode;
|
||||
@override
|
||||
bool get autoLaunch;
|
||||
int get port;
|
||||
@override
|
||||
bool get adminAutoLaunch;
|
||||
bool get autoLaunch;
|
||||
@override
|
||||
bool get systemProxy;
|
||||
@override
|
||||
@@ -1311,6 +1369,10 @@ abstract class _TrayState implements TrayState {
|
||||
String? get locale;
|
||||
@override
|
||||
Brightness? get brightness;
|
||||
@override
|
||||
List<Group> get groups;
|
||||
@override
|
||||
Map<String, String> get map;
|
||||
|
||||
/// Create a copy of TrayState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@@ -3150,7 +3212,6 @@ abstract class _ProxiesActionsState implements ProxiesActionsState {
|
||||
/// @nodoc
|
||||
mixin _$AutoLaunchState {
|
||||
bool get isAutoLaunch => throw _privateConstructorUsedError;
|
||||
bool get isAdminAutoLaunch => throw _privateConstructorUsedError;
|
||||
|
||||
/// Create a copy of AutoLaunchState
|
||||
/// 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) =
|
||||
_$AutoLaunchStateCopyWithImpl<$Res, AutoLaunchState>;
|
||||
@useResult
|
||||
$Res call({bool isAutoLaunch, bool isAdminAutoLaunch});
|
||||
$Res call({bool isAutoLaunch});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@@ -3184,17 +3245,12 @@ class _$AutoLaunchStateCopyWithImpl<$Res, $Val extends AutoLaunchState>
|
||||
@override
|
||||
$Res call({
|
||||
Object? isAutoLaunch = null,
|
||||
Object? isAdminAutoLaunch = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
isAutoLaunch: null == isAutoLaunch
|
||||
? _value.isAutoLaunch
|
||||
: isAutoLaunch // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
isAdminAutoLaunch: null == isAdminAutoLaunch
|
||||
? _value.isAdminAutoLaunch
|
||||
: isAdminAutoLaunch // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
@@ -3207,7 +3263,7 @@ abstract class _$$AutoLaunchStateImplCopyWith<$Res>
|
||||
__$$AutoLaunchStateImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({bool isAutoLaunch, bool isAdminAutoLaunch});
|
||||
$Res call({bool isAutoLaunch});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@@ -3224,17 +3280,12 @@ class __$$AutoLaunchStateImplCopyWithImpl<$Res>
|
||||
@override
|
||||
$Res call({
|
||||
Object? isAutoLaunch = null,
|
||||
Object? isAdminAutoLaunch = null,
|
||||
}) {
|
||||
return _then(_$AutoLaunchStateImpl(
|
||||
isAutoLaunch: null == isAutoLaunch
|
||||
? _value.isAutoLaunch
|
||||
: isAutoLaunch // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
isAdminAutoLaunch: null == isAdminAutoLaunch
|
||||
? _value.isAdminAutoLaunch
|
||||
: isAdminAutoLaunch // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -3242,17 +3293,14 @@ class __$$AutoLaunchStateImplCopyWithImpl<$Res>
|
||||
/// @nodoc
|
||||
|
||||
class _$AutoLaunchStateImpl implements _AutoLaunchState {
|
||||
const _$AutoLaunchStateImpl(
|
||||
{required this.isAutoLaunch, required this.isAdminAutoLaunch});
|
||||
const _$AutoLaunchStateImpl({required this.isAutoLaunch});
|
||||
|
||||
@override
|
||||
final bool isAutoLaunch;
|
||||
@override
|
||||
final bool isAdminAutoLaunch;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'AutoLaunchState(isAutoLaunch: $isAutoLaunch, isAdminAutoLaunch: $isAdminAutoLaunch)';
|
||||
return 'AutoLaunchState(isAutoLaunch: $isAutoLaunch)';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -3261,13 +3309,11 @@ class _$AutoLaunchStateImpl implements _AutoLaunchState {
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$AutoLaunchStateImpl &&
|
||||
(identical(other.isAutoLaunch, isAutoLaunch) ||
|
||||
other.isAutoLaunch == isAutoLaunch) &&
|
||||
(identical(other.isAdminAutoLaunch, isAdminAutoLaunch) ||
|
||||
other.isAdminAutoLaunch == isAdminAutoLaunch));
|
||||
other.isAutoLaunch == isAutoLaunch));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, isAutoLaunch, isAdminAutoLaunch);
|
||||
int get hashCode => Object.hash(runtimeType, isAutoLaunch);
|
||||
|
||||
/// Create a copy of AutoLaunchState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@@ -3280,14 +3326,11 @@ class _$AutoLaunchStateImpl implements _AutoLaunchState {
|
||||
}
|
||||
|
||||
abstract class _AutoLaunchState implements AutoLaunchState {
|
||||
const factory _AutoLaunchState(
|
||||
{required final bool isAutoLaunch,
|
||||
required final bool isAdminAutoLaunch}) = _$AutoLaunchStateImpl;
|
||||
const factory _AutoLaunchState({required final bool isAutoLaunch}) =
|
||||
_$AutoLaunchStateImpl;
|
||||
|
||||
@override
|
||||
bool get isAutoLaunch;
|
||||
@override
|
||||
bool get isAdminAutoLaunch;
|
||||
|
||||
/// Create a copy of AutoLaunchState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
export 'app.dart';
|
||||
export 'clash_config.dart';
|
||||
export 'common.dart';
|
||||
export 'config.dart';
|
||||
export 'core.dart';
|
||||
export 'profile.dart';
|
||||
export 'ffi.dart';
|
||||
export 'selector.dart';
|
||||
export 'common.dart';
|
||||
@@ -96,6 +96,21 @@ extension ProfileExtension on Profile {
|
||||
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 {
|
||||
final response = await request.getFileResponseForUrl(url);
|
||||
final disposition = response.headers.value("content-disposition");
|
||||
@@ -111,12 +126,7 @@ extension ProfileExtension on Profile {
|
||||
if (message.isNotEmpty) {
|
||||
throw message;
|
||||
}
|
||||
final path = await appPath.getProfilePath(id);
|
||||
final file = File(path!);
|
||||
final isExists = await file.exists();
|
||||
if (!isExists) {
|
||||
await file.create(recursive: true);
|
||||
}
|
||||
final file = await getFile();
|
||||
await file.writeAsBytes(bytes);
|
||||
return copyWith(lastUpdateDate: DateTime.now());
|
||||
}
|
||||
@@ -126,12 +136,7 @@ extension ProfileExtension on Profile {
|
||||
if (message.isNotEmpty) {
|
||||
throw message;
|
||||
}
|
||||
final path = await appPath.getProfilePath(id);
|
||||
final file = File(path!);
|
||||
final isExists = await file.exists();
|
||||
if (!isExists) {
|
||||
await file.create(recursive: true);
|
||||
}
|
||||
final file = await getFile();
|
||||
await file.writeAsString(value);
|
||||
return copyWith(lastUpdateDate: DateTime.now());
|
||||
}
|
||||
|
||||
@@ -63,13 +63,15 @@ class ApplicationSelectorState with _$ApplicationSelectorState {
|
||||
class TrayState with _$TrayState {
|
||||
const factory TrayState({
|
||||
required Mode mode,
|
||||
required int port,
|
||||
required bool autoLaunch,
|
||||
required bool adminAutoLaunch,
|
||||
required bool systemProxy,
|
||||
required bool tunEnable,
|
||||
required bool isStart,
|
||||
required String? locale,
|
||||
required Brightness? brightness,
|
||||
required List<Group> groups,
|
||||
required SelectedMap map,
|
||||
}) = _TrayState;
|
||||
}
|
||||
|
||||
@@ -197,7 +199,6 @@ class ProxiesActionsState with _$ProxiesActionsState {
|
||||
class AutoLaunchState with _$AutoLaunchState {
|
||||
const factory AutoLaunchState({
|
||||
required bool isAutoLaunch,
|
||||
required bool isAdminAutoLaunch,
|
||||
}) = _AutoLaunchState;
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ class Vpn {
|
||||
clashCore.requestGc();
|
||||
case "dnsChanged":
|
||||
final dns = call.arguments as String;
|
||||
clashCore.updateDns(dns);
|
||||
clashLib?.updateDns(dns);
|
||||
default:
|
||||
throw MissingPluginException();
|
||||
}
|
||||
@@ -40,7 +40,7 @@ class Vpn {
|
||||
}
|
||||
|
||||
Future<bool?> startVpn() async {
|
||||
final options = clashCore.getAndroidVpnOptions();
|
||||
final options = clashLib?.getAndroidVpnOptions();
|
||||
return await methodChannel.invokeMethod<bool>("start", {
|
||||
'data': json.encode(options),
|
||||
});
|
||||
@@ -54,7 +54,7 @@ class Vpn {
|
||||
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", {
|
||||
"data": json.encode(process),
|
||||
});
|
||||
@@ -79,7 +79,7 @@ class Vpn {
|
||||
receiver!.listen((message) {
|
||||
_handleServiceMessage(message);
|
||||
});
|
||||
clashCore.startTun(fd, receiver!.sendPort.nativePort);
|
||||
clashLib?.startTun(fd, receiver!.sendPort.nativePort);
|
||||
}
|
||||
|
||||
setServiceMessageHandler(ServiceMessageListener serviceMessageListener) {
|
||||
@@ -92,7 +92,7 @@ class Vpn {
|
||||
case ServiceMessageType.protect:
|
||||
_serviceMessageHandler?.onProtect(Fd.fromJson(m.data));
|
||||
case ServiceMessageType.process:
|
||||
_serviceMessageHandler?.onProcess(Process.fromJson(m.data));
|
||||
_serviceMessageHandler?.onProcess(ProcessData.fromJson(m.data));
|
||||
case ServiceMessageType.started:
|
||||
_serviceMessageHandler?.onStarted(m.data);
|
||||
case ServiceMessageType.loaded:
|
||||
|
||||
263
lib/state.dart
263
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/widgets/scaffold.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.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 'common/common.dart';
|
||||
@@ -30,6 +28,8 @@ class GlobalState {
|
||||
late AppController appController;
|
||||
GlobalKey<CommonScaffoldState> homeScaffoldKey = GlobalKey();
|
||||
List<Function> updateFunctionLists = [];
|
||||
bool lastTunEnable = false;
|
||||
int? lastProfileModified;
|
||||
|
||||
bool get isStart => startTime != null && startTime!.isBeforeNow;
|
||||
|
||||
@@ -47,16 +47,61 @@ class GlobalState {
|
||||
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({
|
||||
required AppState appState,
|
||||
required ClashConfig clashConfig,
|
||||
required Config config,
|
||||
bool isPatch = true,
|
||||
}) async {
|
||||
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(
|
||||
UpdateConfigParams(
|
||||
profileId: config.currentProfileId ?? "",
|
||||
config: clashConfig,
|
||||
config: useClashConfig,
|
||||
params: ConfigExtendedParams(
|
||||
isPatch: isPatch,
|
||||
isCompatible: true,
|
||||
@@ -67,14 +112,12 @@ class GlobalState {
|
||||
),
|
||||
);
|
||||
if (res.isNotEmpty) throw res;
|
||||
}
|
||||
|
||||
updateCoreVersionInfo(AppState appState) {
|
||||
appState.versionInfo = clashCore.getVersionInfo();
|
||||
lastTunEnable = useClashConfig.tun.enable;
|
||||
lastProfileModified = await config.getCurrentProfile()?.profileLastModified;
|
||||
}
|
||||
|
||||
handleStart() async {
|
||||
clashCore.start();
|
||||
await clashCore.startListener();
|
||||
if (globalState.isVpnService) {
|
||||
await vpn?.startVpn();
|
||||
startListenUpdate();
|
||||
@@ -85,17 +128,32 @@ class GlobalState {
|
||||
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() {
|
||||
startTime = clashCore.getRunTime();
|
||||
startTime = clashLib?.getRunTime();
|
||||
}
|
||||
|
||||
Future handleStop() async {
|
||||
clashCore.stop();
|
||||
if (Platform.isAndroid) {
|
||||
clashCore.stopTun();
|
||||
}
|
||||
await service?.destroy();
|
||||
startTime = null;
|
||||
await clashCore.stopListener();
|
||||
clashLib?.stopTun();
|
||||
await service?.destroy();
|
||||
stopListenUpdate();
|
||||
}
|
||||
|
||||
@@ -106,6 +164,7 @@ class GlobalState {
|
||||
}) async {
|
||||
clashCore.requestGc();
|
||||
await updateClashConfig(
|
||||
appState: appState,
|
||||
clashConfig: clashConfig,
|
||||
config: config,
|
||||
isPatch: false,
|
||||
@@ -123,30 +182,27 @@ class GlobalState {
|
||||
required Config config,
|
||||
required ClashConfig clashConfig,
|
||||
}) async {
|
||||
appState.isInit = clashCore.isInit;
|
||||
appState.isInit = await clashCore.isInit;
|
||||
if (!appState.isInit) {
|
||||
appState.isInit = await clashService.init(
|
||||
appState.isInit = await clashCore.init(
|
||||
config: config,
|
||||
clashConfig: clashConfig,
|
||||
);
|
||||
if (Platform.isAndroid) {
|
||||
clashCore.setState(
|
||||
CoreState(
|
||||
enable: config.vpnProps.enable,
|
||||
accessControl: config.isAccessControl ? config.accessControl : null,
|
||||
ipv6: config.vpnProps.ipv6,
|
||||
allowBypass: config.vpnProps.allowBypass,
|
||||
systemProxy: config.vpnProps.systemProxy,
|
||||
onlyProxy: config.appSetting.onlyProxy,
|
||||
bypassDomain: config.networkProps.bypassDomain,
|
||||
routeAddress: clashConfig.routeAddress,
|
||||
currentProfileName:
|
||||
config.currentProfile?.label ?? config.currentProfileId ?? "",
|
||||
),
|
||||
);
|
||||
}
|
||||
clashLib?.setState(
|
||||
CoreState(
|
||||
enable: config.vpnProps.enable,
|
||||
accessControl: config.isAccessControl ? config.accessControl : null,
|
||||
ipv6: config.vpnProps.ipv6,
|
||||
allowBypass: config.vpnProps.allowBypass,
|
||||
systemProxy: config.vpnProps.systemProxy,
|
||||
onlyProxy: config.appSetting.onlyProxy,
|
||||
bypassDomain: config.networkProps.bypassDomain,
|
||||
routeAddress: clashConfig.routeAddress,
|
||||
currentProfileName:
|
||||
config.currentProfile?.label ?? config.currentProfileId ?? "",
|
||||
),
|
||||
);
|
||||
}
|
||||
updateCoreVersionInfo(appState);
|
||||
}
|
||||
|
||||
Future<void> updateGroups(AppState appState) async {
|
||||
@@ -198,8 +254,8 @@ class GlobalState {
|
||||
required Config config,
|
||||
required String groupName,
|
||||
required String proxyName,
|
||||
}) {
|
||||
clashCore.changeProxy(
|
||||
}) async {
|
||||
await clashCore.changeProxy(
|
||||
ChangeProxyParams(
|
||||
groupName: groupName,
|
||||
proxyName: proxyName,
|
||||
@@ -226,18 +282,21 @@ class GlobalState {
|
||||
}
|
||||
|
||||
updateTraffic({
|
||||
required Config config,
|
||||
AppFlowingState? appFlowingState,
|
||||
}) {
|
||||
final traffic = clashCore.getTraffic();
|
||||
}) async {
|
||||
final onlyProxy = config.appSetting.onlyProxy;
|
||||
final traffic = await clashCore.getTraffic(onlyProxy);
|
||||
if (Platform.isAndroid && isVpnService == true) {
|
||||
vpn?.startForeground(
|
||||
title: clashCore.getCurrentProfileName(),
|
||||
title: clashLib?.getCurrentProfileName() ?? "",
|
||||
content: "$traffic",
|
||||
);
|
||||
} else {
|
||||
if (appFlowingState != null) {
|
||||
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();
|
||||
|
||||
@@ -135,9 +135,6 @@ class CommonCard extends StatelessWidget {
|
||||
if (isSelected) {
|
||||
return colorScheme.secondaryContainer;
|
||||
}
|
||||
if (states.isEmpty) {
|
||||
return colorScheme.surfaceContainerLow;
|
||||
}
|
||||
return colorScheme.surfaceContainer;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ class CommonScaffoldState extends State<CommonScaffold> {
|
||||
}
|
||||
}
|
||||
|
||||
set floatingActionButton(Widget floatingActionButton) {
|
||||
set floatingActionButton(Widget? floatingActionButton) {
|
||||
if (_floatingActionButton.value != floatingActionButton) {
|
||||
_floatingActionButton.value = floatingActionButton;
|
||||
}
|
||||
@@ -122,25 +122,27 @@ class CommonScaffoldState extends State<CommonScaffold> {
|
||||
valueListenable: _actions,
|
||||
builder: (_, actions, __) {
|
||||
final realActions =
|
||||
actions.isNotEmpty ? actions : widget.actions;
|
||||
actions.isNotEmpty ? actions : widget.actions;
|
||||
return AppBar(
|
||||
centerTitle: false,
|
||||
systemOverlayStyle: SystemUiOverlayStyle(
|
||||
statusBarColor: Colors.transparent,
|
||||
statusBarIconBrightness:
|
||||
Theme.of(context).brightness == Brightness.dark
|
||||
? Brightness.light
|
||||
: Brightness.dark,
|
||||
Theme.of(context).brightness == Brightness.dark
|
||||
? Brightness.light
|
||||
: Brightness.dark,
|
||||
systemNavigationBarIconBrightness:
|
||||
Theme.of(context).brightness == Brightness.dark
|
||||
? Brightness.light
|
||||
: Brightness.dark,
|
||||
systemNavigationBarColor: widget.bottomNavigationBar != null
|
||||
? context.colorScheme.surfaceContainer
|
||||
: context.colorScheme.surface,
|
||||
Theme.of(context).brightness == Brightness.dark
|
||||
? Brightness.light
|
||||
: Brightness.dark,
|
||||
systemNavigationBarColor:
|
||||
widget.bottomNavigationBar != null
|
||||
? context.colorScheme.surfaceContainer
|
||||
: context.colorScheme.surface,
|
||||
systemNavigationBarDividerColor: Colors.transparent,
|
||||
),
|
||||
automaticallyImplyLeading: widget.automaticallyImplyLeading,
|
||||
automaticallyImplyLeading:
|
||||
widget.automaticallyImplyLeading,
|
||||
leading: widget.leading,
|
||||
title: Text(widget.title),
|
||||
actions: [
|
||||
|
||||
@@ -119,7 +119,7 @@ install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||
|
||||
# libclash.so
|
||||
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)
|
||||
|
||||
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 */; };
|
||||
33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; };
|
||||
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, ); }; };
|
||||
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 */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@@ -58,12 +57,39 @@
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
7AC277AB2B90DFD900E026B1 /* libclash.dylib in Bundle Framework */,
|
||||
7AC6855B2B8AF836004C123B /* (null) in Bundle Framework */,
|
||||
);
|
||||
name = "Bundle Framework";
|
||||
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 */
|
||||
|
||||
/* 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; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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 */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -109,7 +135,6 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
7AC277AA2B90DE1400E026B1 /* libclash.dylib in Frameworks */,
|
||||
CDD319C761C7664F6008596B /* Pods_Runner.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -139,6 +164,7 @@
|
||||
33CC10E42044A3C60003C045 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F50091042CF74B7700D43AEA /* FlClashCore */,
|
||||
33FAB671232836740065AC1E /* Runner */,
|
||||
33CEB47122A05771004F2AC0 /* Flutter */,
|
||||
331C80D6294CF71000263BE5 /* RunnerTests */,
|
||||
@@ -208,7 +234,6 @@
|
||||
D73912EC22F37F3D000D13A0 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7AC277A92B90DE1400E026B1 /* libclash.dylib */,
|
||||
4121E8CCDC7DC35194714CDE /* Pods_Runner.framework */,
|
||||
72CBDF47BB69EDEFE644C48D /* Pods_RunnerTests.framework */,
|
||||
);
|
||||
@@ -226,6 +251,7 @@
|
||||
331C80D1294CF70F00263BE5 /* Sources */,
|
||||
331C80D2294CF70F00263BE5 /* Frameworks */,
|
||||
331C80D3294CF70F00263BE5 /* Resources */,
|
||||
F5FAC0AA2CEDC4DA000CF079 /* CopyFiles */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@@ -248,6 +274,8 @@
|
||||
33CC110E2044A8840003C045 /* Bundle Framework */,
|
||||
3399D490228B24CF009A79C7 /* ShellScript */,
|
||||
1522C6AC211009D2A7DFAD40 /* [CP] Embed Pods Frameworks */,
|
||||
F5FAC0AE2CEDC891000CF079 /* CopyFiles */,
|
||||
F50091032CF74B6400D43AEA /* CopyFiles */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@@ -582,7 +610,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
LIBRARY_SEARCH_PATHS = "${SRCROOT}/../libclash/macos/";
|
||||
LIBRARY_SEARCH_PATHS = "";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.follow.clash;
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_VERSION = 5.0;
|
||||
@@ -710,7 +738,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
LIBRARY_SEARCH_PATHS = "${SRCROOT}/../libclash/macos/";
|
||||
LIBRARY_SEARCH_PATHS = "";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.follow.clash;
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
@@ -732,7 +760,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
LIBRARY_SEARCH_PATHS = "${SRCROOT}/../libclash/macos/";
|
||||
LIBRARY_SEARCH_PATHS = "";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.follow.clash;
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_VERSION = 5.0;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
name: fl_clash
|
||||
description: A multi-platform proxy client based on ClashMeta, simple and easy to use, open-source and ad-free.
|
||||
publish_to: 'none'
|
||||
version: 0.8.68+202411171
|
||||
version: 0.8.69+202412061
|
||||
environment:
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
320
setup.dart
320
setup.dart
@@ -2,115 +2,121 @@
|
||||
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:args/command_runner.dart';
|
||||
import 'package:path/path.dart';
|
||||
|
||||
enum PlatformType {
|
||||
enum Target {
|
||||
windows,
|
||||
linux,
|
||||
android,
|
||||
macos,
|
||||
}
|
||||
|
||||
enum Arch { amd64, arm64, arm }
|
||||
|
||||
class BuildLibItem {
|
||||
PlatformType platform;
|
||||
Arch arch;
|
||||
String archName;
|
||||
|
||||
BuildLibItem({
|
||||
required this.platform,
|
||||
required this.arch,
|
||||
required this.archName,
|
||||
});
|
||||
extension TargetExt on Target {
|
||||
String get os {
|
||||
if (this == Target.macos) {
|
||||
return "darwin";
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
String get dynamicLibExtensionName {
|
||||
final String extensionName;
|
||||
switch (platform) {
|
||||
case PlatformType.android || PlatformType.linux:
|
||||
extensionName = "so";
|
||||
switch (this) {
|
||||
case Target.android || Target.linux:
|
||||
extensionName = ".so";
|
||||
break;
|
||||
case PlatformType.windows:
|
||||
extensionName = "dll";
|
||||
case Target.windows:
|
||||
extensionName = ".dll";
|
||||
break;
|
||||
case PlatformType.macos:
|
||||
extensionName = "dylib";
|
||||
case Target.macos:
|
||||
extensionName = ".dylib";
|
||||
break;
|
||||
}
|
||||
return extensionName;
|
||||
}
|
||||
|
||||
String get os {
|
||||
if (platform == PlatformType.macos) {
|
||||
return "darwin";
|
||||
String get executableExtensionName {
|
||||
final String extensionName;
|
||||
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
|
||||
String toString() {
|
||||
return 'BuildLibItem{platform: $platform, arch: $arch, archName: $archName}';
|
||||
return 'BuildLibItem{target: $target, arch: $arch, archName: $archName}';
|
||||
}
|
||||
}
|
||||
|
||||
class Build {
|
||||
static List<BuildLibItem> get buildItems => [
|
||||
BuildLibItem(
|
||||
platform: PlatformType.macos,
|
||||
arch: Arch.amd64,
|
||||
archName: '',
|
||||
static List<BuildItem> get buildItems => [
|
||||
BuildItem(
|
||||
target: Target.macos,
|
||||
),
|
||||
BuildLibItem(
|
||||
platform: PlatformType.macos,
|
||||
arch: Arch.arm64,
|
||||
archName: '',
|
||||
BuildItem(
|
||||
target: Target.windows,
|
||||
),
|
||||
BuildLibItem(
|
||||
platform: PlatformType.windows,
|
||||
arch: Arch.amd64,
|
||||
archName: '',
|
||||
BuildItem(
|
||||
target: Target.linux,
|
||||
),
|
||||
BuildLibItem(
|
||||
platform: PlatformType.windows,
|
||||
arch: Arch.arm64,
|
||||
archName: '',
|
||||
),
|
||||
BuildLibItem(
|
||||
platform: PlatformType.android,
|
||||
BuildItem(
|
||||
target: Target.android,
|
||||
arch: Arch.arm,
|
||||
archName: 'armeabi-v7a',
|
||||
),
|
||||
BuildLibItem(
|
||||
platform: PlatformType.android,
|
||||
BuildItem(
|
||||
target: Target.android,
|
||||
arch: Arch.arm64,
|
||||
archName: 'arm64-v8a',
|
||||
),
|
||||
BuildLibItem(
|
||||
platform: PlatformType.android,
|
||||
BuildItem(
|
||||
target: Target.android,
|
||||
arch: Arch.amd64,
|
||||
archName: 'x86_64',
|
||||
),
|
||||
BuildLibItem(
|
||||
platform: PlatformType.linux,
|
||||
arch: Arch.amd64,
|
||||
archName: '',
|
||||
),
|
||||
];
|
||||
|
||||
static String get appName => "FlClash";
|
||||
|
||||
static String get coreName => "FlClashCore";
|
||||
|
||||
static String get libName => "libclash";
|
||||
|
||||
static String get outDir => join(current, libName);
|
||||
|
||||
static String get _coreDir => join(current, "core");
|
||||
|
||||
static String get _servicesDir => join(current, "services", "helper");
|
||||
|
||||
static String get distPath => join(current, "dist");
|
||||
|
||||
static String _getCc(BuildLibItem buildItem) {
|
||||
static String _getCc(BuildItem buildItem) {
|
||||
final environment = Platform.environment;
|
||||
if (buildItem.platform == PlatformType.android) {
|
||||
if (buildItem.target == Target.android) {
|
||||
final ndk = environment["ANDROID_NDK"];
|
||||
assert(ndk != null);
|
||||
final prebuiltDir =
|
||||
@@ -158,55 +164,97 @@ class Build {
|
||||
if (exitCode != 0 && name != null) throw "$name error";
|
||||
}
|
||||
|
||||
static buildLib({
|
||||
required PlatformType platform,
|
||||
static buildCore({
|
||||
required Mode mode,
|
||||
required Target target,
|
||||
Arch? arch,
|
||||
}) async {
|
||||
final isLib = mode == Mode.lib;
|
||||
|
||||
final items = buildItems.where(
|
||||
(element) {
|
||||
return element.platform == platform &&
|
||||
return element.target == target &&
|
||||
(arch == null ? true : element.arch == arch);
|
||||
},
|
||||
).toList();
|
||||
|
||||
for (final item in items) {
|
||||
final outFileDir = join(
|
||||
outDir,
|
||||
item.platform.name,
|
||||
item.target.name,
|
||||
item.archName,
|
||||
);
|
||||
|
||||
final file = File(outFileDir);
|
||||
if (file.existsSync()) {
|
||||
file.deleteSync(recursive: true);
|
||||
}
|
||||
|
||||
final fileName = isLib
|
||||
? "$libName${item.target.dynamicLibExtensionName}"
|
||||
: "$coreName${item.target.executableExtensionName}";
|
||||
final outPath = join(
|
||||
outFileDir,
|
||||
"$libName.${item.dynamicLibExtensionName}",
|
||||
fileName,
|
||||
);
|
||||
final Map<String, String> env = {};
|
||||
env["GOOS"] = item.os;
|
||||
env["GOARCH"] = item.arch.name;
|
||||
env["CGO_ENABLED"] = "1";
|
||||
env["CC"] = _getCc(item);
|
||||
env["CFLAGS"] = "-O3 -Werror";
|
||||
|
||||
final Map<String, String> env = {};
|
||||
env["GOOS"] = item.target.os;
|
||||
|
||||
if (isLib) {
|
||||
if (item.arch != null) {
|
||||
env["GOARCH"] = item.arch!.name;
|
||||
}
|
||||
env["CGO_ENABLED"] = "1";
|
||||
env["CC"] = _getCc(item);
|
||||
env["CFLAGS"] = "-O3 -Werror";
|
||||
} else {
|
||||
env["CGO_ENABLED"] = "0";
|
||||
}
|
||||
|
||||
final execLines = [
|
||||
"go",
|
||||
"build",
|
||||
"-ldflags=-w -s",
|
||||
"-tags=$tags",
|
||||
if (isLib) "-buildmode=c-shared",
|
||||
"-o",
|
||||
outPath,
|
||||
];
|
||||
await exec(
|
||||
[
|
||||
"go",
|
||||
"build",
|
||||
"-ldflags=-w -s",
|
||||
"-tags=$tags",
|
||||
"-buildmode=c-shared",
|
||||
"-o",
|
||||
outPath,
|
||||
],
|
||||
name: "build libclash",
|
||||
execLines,
|
||||
name: "build core",
|
||||
environment: env,
|
||||
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) {
|
||||
print(command);
|
||||
return command.split(" ");
|
||||
}
|
||||
|
||||
@@ -250,22 +298,30 @@ class Build {
|
||||
}
|
||||
|
||||
class BuildCommand extends Command {
|
||||
PlatformType platform;
|
||||
Target target;
|
||||
|
||||
BuildCommand({
|
||||
required this.platform,
|
||||
required this.target,
|
||||
}) {
|
||||
if (target == Target.android) {
|
||||
argParser.addOption(
|
||||
"arch",
|
||||
valueHelp: arches.map((e) => e.name).join(','),
|
||||
help: 'The $name build desc',
|
||||
);
|
||||
} else {
|
||||
argParser.addOption(
|
||||
"arch",
|
||||
help: 'The $name build archName',
|
||||
);
|
||||
}
|
||||
|
||||
argParser.addOption(
|
||||
"build",
|
||||
"out",
|
||||
valueHelp: [
|
||||
'all',
|
||||
'lib',
|
||||
"app",
|
||||
"core",
|
||||
].join(','),
|
||||
help: 'The $name build type',
|
||||
);
|
||||
argParser.addOption(
|
||||
"arch",
|
||||
valueHelp: arches.map((e) => e.name).join(','),
|
||||
help: 'The $name build arch',
|
||||
);
|
||||
}
|
||||
@@ -274,17 +330,13 @@ class BuildCommand extends Command {
|
||||
String get description => "build $name application";
|
||||
|
||||
@override
|
||||
String get name => platform.name;
|
||||
String get name => target.name;
|
||||
|
||||
List<Arch> get arches => Build.buildItems
|
||||
.where((element) => element.platform == platform)
|
||||
.map((e) => e.arch)
|
||||
.where((element) => element.target == target && element.arch != null)
|
||||
.map((e) => e.arch!)
|
||||
.toList();
|
||||
|
||||
Future<void> _buildLib(Arch? arch) async {
|
||||
await Build.buildLib(platform: platform, arch: arch);
|
||||
}
|
||||
|
||||
_getLinuxDependencies() async {
|
||||
await Build.exec(
|
||||
Build.getExecutable("sudo apt update -y"),
|
||||
@@ -331,51 +383,71 @@ class BuildCommand extends Command {
|
||||
}
|
||||
|
||||
_buildDistributor({
|
||||
required PlatformType platform,
|
||||
required Target target,
|
||||
required String targets,
|
||||
String args = '',
|
||||
}) async {
|
||||
await Build.getDistributor();
|
||||
/* final tag = Platform.environment["TAG"] ?? "+";
|
||||
final isDev = tag.contains("+");
|
||||
final channelArgs = isDev && platform == PlatformType.android ? "--build-flavor dev" : "";*/
|
||||
await Build.exec(
|
||||
name: name,
|
||||
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
|
||||
Future<void> run() async {
|
||||
final String build = argResults?['build'] ?? 'all';
|
||||
final archName = argResults?['arch'];
|
||||
final currentArches =
|
||||
arches.where((element) => element.name == archName).toList();
|
||||
final arch = currentArches.isEmpty ? null : currentArches.first;
|
||||
if (arch == null && platform == PlatformType.windows) {
|
||||
throw "Invalid arch";
|
||||
final mode = target == Target.android ? Mode.lib : Mode.core;
|
||||
final String out = argResults?['out'] ?? 'app';
|
||||
Arch? arch;
|
||||
var archName = argResults?['arch'];
|
||||
|
||||
if (target == Target.android) {
|
||||
final currentArches =
|
||||
arches.where((element) => element.name == archName).toList();
|
||||
arch = currentArches.isEmpty ? null : currentArches.first;
|
||||
}
|
||||
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;
|
||||
}
|
||||
switch (platform) {
|
||||
case PlatformType.windows:
|
||||
|
||||
switch (target) {
|
||||
case Target.windows:
|
||||
_buildDistributor(
|
||||
platform: platform,
|
||||
target: target,
|
||||
targets: "exe,zip",
|
||||
args: "--description ${arch!.name}",
|
||||
args: "--description $archName",
|
||||
);
|
||||
case PlatformType.linux:
|
||||
case Target.linux:
|
||||
await _getLinuxDependencies();
|
||||
_buildDistributor(
|
||||
platform: platform,
|
||||
target: target,
|
||||
targets: "appimage,deb,rpm",
|
||||
args: "--description ${arch!.name}",
|
||||
args: "--description $archName",
|
||||
);
|
||||
case PlatformType.android:
|
||||
case Target.android:
|
||||
final targetMap = {
|
||||
Arch.arm: "android-arm",
|
||||
Arch.arm64: "android-arm64",
|
||||
@@ -387,17 +459,17 @@ class BuildCommand extends Command {
|
||||
.map((e) => targetMap[e])
|
||||
.toList();
|
||||
_buildDistributor(
|
||||
platform: platform,
|
||||
target: target,
|
||||
targets: "apk",
|
||||
args:
|
||||
"--flutter-build-args split-per-abi --build-target-platform ${defaultTargets.join(",")}",
|
||||
);
|
||||
case PlatformType.macos:
|
||||
case Target.macos:
|
||||
await _getMacosDependencies();
|
||||
_buildDistributor(
|
||||
platform: platform,
|
||||
target: target,
|
||||
targets: "dmg",
|
||||
args: "--description ${arch!.name}",
|
||||
args: "--description $archName",
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -405,15 +477,15 @@ class BuildCommand extends Command {
|
||||
|
||||
main(args) async {
|
||||
final runner = CommandRunner("setup", "build Application");
|
||||
runner.addCommand(BuildCommand(platform: PlatformType.android));
|
||||
runner.addCommand(BuildCommand(target: Target.android));
|
||||
if (Platform.isWindows) {
|
||||
runner.addCommand(BuildCommand(platform: PlatformType.windows));
|
||||
runner.addCommand(BuildCommand(target: Target.windows));
|
||||
}
|
||||
if (Platform.isLinux) {
|
||||
runner.addCommand(BuildCommand(platform: PlatformType.linux));
|
||||
runner.addCommand(BuildCommand(target: Target.linux));
|
||||
}
|
||||
if (Platform.isMacOS) {
|
||||
runner.addCommand(BuildCommand(platform: PlatformType.macos));
|
||||
runner.addCommand(BuildCommand(target: Target.macos));
|
||||
}
|
||||
runner.run(args);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,35 @@
|
||||
// ignore_for_file: avoid_print
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
void main() {
|
||||
final cmdList = [];
|
||||
final ignoreHosts = "\"ass\"";
|
||||
cmdList.add(
|
||||
["gsettings", "set", "org.gnome.system.proxy", "port", ignoreHosts],
|
||||
Future<void> main() async {
|
||||
// final cmdList = [];
|
||||
// final ignoreHosts = "\"ass\"";
|
||||
// cmdList.add(
|
||||
// ["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 {
|
||||
|
||||
@@ -90,7 +90,10 @@ set(CLASH_DIR "../libclash/windows")
|
||||
# set(CLASH_DIR "../libclash/windows/x86")
|
||||
# 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)
|
||||
|
||||
install(FILES "EnableLoopback.exe" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||
|
||||
Reference in New Issue
Block a user