package main /* #include */ import "C" import ( bridge "core/dart-bridge" "encoding/json" "fmt" "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/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/log" "github.com/metacubex/mihomo/tunnel" "github.com/metacubex/mihomo/tunnel/statistic" "golang.org/x/net/context" ) var currentRawConfig = config.DefaultRawConfig() 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() 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) 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.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.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 { return false, nil } expectedStatus, err := utils.NewUnsignedRanges[uint16]("") if err != nil { 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 getProviders func getProviders() *C.char { data, err := json.Marshal(tunnel.Providers()) var msg *C.char if err != nil { msg = C.CString("") return msg } msg = C.CString(string(data)) return msg } //export getProvider func getProvider(name *C.char) *C.char { providerName := C.GoString(name) providers := tunnel.Providers() data, err := json.Marshal(providers[providerName]) if err != nil { return C.CString("") } return C.CString(string(data)) } //export getExternalProviders func getExternalProviders() *C.char { 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 { 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() { switch geoTypeString { case "MMDB": err := updater.UpdateMMDB(constant.Path.Resolve(geoNameString)) if err != nil { bridge.SendToPort(i, err.Error()) return } case "ASN": err := updater.UpdateASN(constant.Path.Resolve(geoNameString)) if err != nil { bridge.SendToPort(i, err.Error()) return } case "GeoIp": err := updater.UpdateGeoIp(constant.Path.Resolve(geoNameString)) if err != nil { bridge.SendToPort(i, err.Error()) return } case "GeoSite": err := updater.UpdateGeoSite(constant.Path.Resolve(geoNameString)) 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) } //export initMessage func initMessage(port C.longlong) { i := int64(port) Port = i } //export freeCString 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, }) } }