import 'dart:async'; import 'dart:convert'; import 'dart:ffi'; import 'dart:io'; import 'dart:isolate'; import 'package:ffi/ffi.dart'; import '../enum/enum.dart'; import '../models/models.dart'; import '../common/common.dart'; import 'generated/clash_ffi.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"; } ClashCore._internal() { lib = _getClashLib(); clashFFI = ClashFFI(lib); clashFFI.initNativeApiBridge( NativeApi.initializeApiDLData, receiver.sendPort.nativePort, ); } factory ClashCore() { _instance ??= ClashCore._internal(); return _instance!; } bool init(String homeDir) { return clashFFI.initClash( homeDir.toNativeUtf8().cast(), ) == 1; } shutdown() { clashFFI.shutdownClash(); lib.close(); } bool get isInit => clashFFI.getIsInit() == 1; bool validateConfig(String data) { return clashFFI.validateConfig( data.toNativeUtf8().cast(), ) == 1; } Future updateConfig(UpdateConfigParams updateConfigParams) { final completer = Completer(); final receiver = ReceivePort(); receiver.listen((message) { if(!completer.isCompleted){ completer.complete(message); receiver.close(); } }); final params = json.encode(updateConfigParams); clashFFI.updateConfig( params.toNativeUtf8().cast(), receiver.sendPort.nativePort, ); return completer.future; } Future> getProxiesGroups() { final proxiesRaw = clashFFI.getProxies(); final proxiesRawString = proxiesRaw.cast().toDartString(); return Isolate.run>(() { final proxies = json.decode(proxiesRawString); final groupNames = [ UsedProxy.GLOBAL.name, ...(proxies[UsedProxy.GLOBAL.name]["all"] as List).where((e) { final proxy = proxies[e]; return GroupTypeExtension.valueList.contains(proxy['type']); }) ]; final groupsRaw = groupNames.map((groupName) { final group = proxies[groupName]; group["all"] = ((group["all"] ?? []) as List) .where( (name) => !groupNames.contains(groupNames), ) .map( (name) => proxies[name], ) .toList(); return group; }).toList(); return groupsRaw.map((e) => Group.fromJson(e)).toList(); }); } bool changeProxy(ChangeProxyParams changeProxyParams) { final params = json.encode(changeProxyParams); return clashFFI.changeProxy(params.toNativeUtf8().cast()) == 1; } bool delay(String proxyName) { final delayParams = { "proxy-name": proxyName, "timeout": appConstant.httpTimeoutDuration.inMilliseconds, }; clashFFI.asyncTestDelay(json.encode(delayParams).toNativeUtf8().cast()); return true; } VersionInfo getVersionInfo() { final versionInfoRaw = clashFFI.getVersionInfo(); final versionInfo = json.decode(versionInfoRaw.cast().toDartString()); return VersionInfo.fromJson(versionInfo); } Traffic getTraffic() { final trafficRaw = clashFFI.getTraffic(); final trafficMap = json.decode(trafficRaw.cast().toDartString()); return Traffic.fromMap(trafficMap); } void startLog() { clashFFI.startLog(); } stopLog() { clashFFI.stopLog(); } startTun(int fd) { clashFFI.startTUN(fd); } void stopTun() { clashFFI.stopTun(); } List getConnections() { final connectionsDataRaw = clashFFI.getConnections(); final connectionsData = json.decode(connectionsDataRaw.cast().toDartString()) as Map; final connectionsRaw = connectionsData['connections'] as List? ?? []; return connectionsRaw.map((e) => Connection.fromJson(e)).toList(); } } final clashCore = ClashCore();