Files
MWClash/lib/clash/core.dart

391 lines
11 KiB
Dart
Raw Normal View History

2024-05-04 21:51:40 +08:00
import 'dart:async';
2024-04-30 23:38:49 +08:00
import 'dart:convert';
import 'dart:ffi';
import 'dart:io';
import 'dart:isolate';
import 'package:ffi/ffi.dart';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart';
2024-04-30 23:38:49 +08:00
import 'generated/clash_ffi.dart';
class ClashCore {
static ClashCore? _instance;
static final receiver = ReceivePort();
late final ClashFFI clashFFI;
late final DynamicLibrary lib;
2024-05-04 21:51:40 +08:00
DynamicLibrary _getClashLib() {
2024-04-30 23:38:49 +08:00
if (Platform.isWindows) {
2024-05-04 21:51:40 +08:00
return DynamicLibrary.open("libclash.dll");
2024-04-30 23:38:49 +08:00
}
if (Platform.isMacOS) {
2024-05-04 21:51:40 +08:00
return DynamicLibrary.open("libclash.dylib");
2024-04-30 23:38:49 +08:00
}
if (Platform.isAndroid || Platform.isLinux) {
2024-05-04 21:51:40 +08:00
return DynamicLibrary.open("libclash.so");
2024-04-30 23:38:49 +08:00
}
2024-05-04 21:51:40 +08:00
throw "Platform is not supported";
}
ClashCore._internal() {
lib = _getClashLib();
clashFFI = ClashFFI(lib);
2024-04-30 23:38:49 +08:00
clashFFI.initNativeApiBridge(
NativeApi.initializeApiDLData,
);
}
factory ClashCore() {
_instance ??= ClashCore._internal();
return _instance!;
}
bool init(String homeDir) {
2024-06-28 07:45:50 +08:00
final homeDirChar = homeDir.toNativeUtf8().cast<Char>();
final isInit = clashFFI.initClash(homeDirChar) == 1;
malloc.free(homeDirChar);
return isInit;
2024-04-30 23:38:49 +08:00
}
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();
}
});
2024-06-28 07:45:50 +08:00
final dataChar = data.toNativeUtf8().cast<Char>();
clashFFI.validateConfig(
2024-06-28 07:45:50 +08:00
dataChar,
receiver.sendPort.nativePort,
);
2024-06-28 07:45:50 +08:00
malloc.free(dataChar);
return completer.future;
2024-04-30 23:38:49 +08:00
}
2024-05-05 02:40:35 +08:00
Future<String> updateConfig(UpdateConfigParams updateConfigParams) {
final completer = Completer<String>();
final receiver = ReceivePort();
receiver.listen((message) {
if (!completer.isCompleted) {
2024-05-05 02:40:35 +08:00
completer.complete(message);
receiver.close();
}
});
2024-04-30 23:38:49 +08:00
final params = json.encode(updateConfigParams);
2024-06-28 07:45:50 +08:00
final paramsChar = params.toNativeUtf8().cast<Char>();
2024-05-05 02:40:35 +08:00
clashFFI.updateConfig(
2024-06-28 07:45:50 +08:00
paramsChar,
2024-05-05 02:40:35 +08:00
receiver.sendPort.nativePort,
);
2024-06-28 07:45:50 +08:00
malloc.free(paramsChar);
2024-05-05 02:40:35 +08:00
return completer.future;
2024-04-30 23:38:49 +08:00
}
2024-07-13 16:36:08 +08:00
initMessage() {
clashFFI.initMessage(
receiver.sendPort.nativePort,
);
}
2024-05-03 14:31:10 +08:00
Future<List<Group>> getProxiesGroups() {
2024-04-30 23:38:49 +08:00
final proxiesRaw = clashFFI.getProxies();
2024-05-03 14:31:10 +08:00
final proxiesRawString = proxiesRaw.cast<Utf8>().toDartString();
2024-06-28 07:45:50 +08:00
clashFFI.freeCString(proxiesRaw);
2024-05-03 14:31:10 +08:00
return Isolate.run<List<Group>>(() {
2024-06-28 07:45:50 +08:00
if (proxiesRawString.isEmpty) return [];
final proxies = (json.decode(proxiesRawString) ?? {}) as Map;
2024-06-28 07:45:50 +08:00
if (proxies.isEmpty) return [];
2024-05-04 00:14:07 +08:00
final groupNames = [
UsedProxy.GLOBAL.name,
...(proxies[UsedProxy.GLOBAL.name]["all"] as List).where((e) {
final proxy = proxies[e] ?? {};
return GroupTypeExtension.valueList.contains(proxy['type']);
2024-05-04 00:14:07 +08:00
})
];
final groupsRaw = groupNames.map((groupName) {
2024-05-03 14:31:10 +08:00
final group = proxies[groupName];
group["all"] = ((group["all"] ?? []) as List)
2024-04-30 23:38:49 +08:00
.map(
(name) => proxies[name],
2024-06-28 07:45:50 +08:00
)
.where((proxy) => proxy != null)
2024-04-30 23:38:49 +08:00
.toList();
2024-05-03 14:31:10 +08:00
return group;
}).toList();
return groupsRaw
.map(
(e) => Group.fromJson(e),
)
.toList();
2024-05-03 14:31:10 +08:00
});
2024-04-30 23:38:49 +08:00
}
Future<List<ExternalProvider>> getExternalProviders() {
final externalProvidersRaw = clashFFI.getExternalProviders();
final externalProvidersRawString =
2024-06-28 07:45:50 +08:00
externalProvidersRaw.cast<Utf8>().toDartString();
clashFFI.freeCString(externalProvidersRaw);
return Isolate.run<List<ExternalProvider>>(() {
final externalProviders =
2024-08-05 19:25:35 +08:00
(json.decode(externalProvidersRawString) as List<dynamic>)
2024-06-28 07:45:50 +08:00
.map(
(item) => ExternalProvider.fromJson(item),
)
.toList();
return externalProviders;
});
}
2024-08-05 19:25:35 +08:00
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));
}
2024-08-05 19:25:35 +08:00
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;
}
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;
}
Future<String> updateExternalProvider({
required String providerName,
}) {
final completer = Completer<String>();
final receiver = ReceivePort();
receiver.listen((message) {
if (!completer.isCompleted) {
completer.complete(message);
receiver.close();
}
});
2024-06-28 07:45:50 +08:00
final providerNameChar = providerName.toNativeUtf8().cast<Char>();
clashFFI.updateExternalProvider(
2024-06-28 07:45:50 +08:00
providerNameChar,
receiver.sendPort.nativePort,
);
2024-06-28 07:45:50 +08:00
malloc.free(providerNameChar);
return completer.future;
}
changeProxy(ChangeProxyParams changeProxyParams) {
2024-04-30 23:38:49 +08:00
final params = json.encode(changeProxyParams);
2024-06-28 07:45:50 +08:00
final paramsChar = params.toNativeUtf8().cast<Char>();
clashFFI.changeProxy(paramsChar);
2024-06-28 07:45:50 +08:00
malloc.free(paramsChar);
2024-04-30 23:38:49 +08:00
}
start() {
clashFFI.start();
}
stop() {
clashFFI.stop();
}
2024-06-06 16:31:08 +08:00
Future<Delay> getDelay(String proxyName) {
2024-04-30 23:38:49 +08:00
final delayParams = {
"proxy-name": proxyName,
"timeout": httpTimeoutDuration.inMilliseconds,
2024-04-30 23:38:49 +08:00
};
2024-06-06 16:31:08 +08:00
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>();
2024-06-06 16:31:08 +08:00
clashFFI.asyncTestDelay(
2024-06-28 07:45:50 +08:00
delayParamsChar,
2024-06-06 16:31:08 +08:00
receiver.sendPort.nativePort,
);
2024-06-28 07:45:50 +08:00
malloc.free(delayParamsChar);
2024-06-06 16:31:08 +08:00
return completer.future;
}
clearEffect(String profileId) {
final profileIdChar = profileId.toNativeUtf8().cast<Char>();
clashFFI.clearEffect(profileIdChar);
malloc.free(profileIdChar);
2024-04-30 23:38:49 +08:00
}
VersionInfo getVersionInfo() {
final versionInfoRaw = clashFFI.getVersionInfo();
final versionInfo = json.decode(versionInfoRaw.cast<Utf8>().toDartString());
2024-06-28 07:45:50 +08:00
clashFFI.freeCString(versionInfoRaw);
2024-04-30 23:38:49 +08:00
return VersionInfo.fromJson(versionInfo);
}
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);
}
2024-04-30 23:38:49 +08:00
Traffic getTraffic() {
final trafficRaw = clashFFI.getTraffic();
final trafficMap = json.decode(trafficRaw.cast<Utf8>().toDartString());
2024-06-28 07:45:50 +08:00
clashFFI.freeCString(trafficRaw);
2024-04-30 23:38:49 +08:00
return Traffic.fromMap(trafficMap);
}
Traffic getTotalTraffic() {
final trafficRaw = clashFFI.getTotalTraffic();
final trafficMap = json.decode(trafficRaw.cast<Utf8>().toDartString());
2024-06-28 07:45:50 +08:00
clashFFI.freeCString(trafficRaw);
return Traffic.fromMap(trafficMap);
}
2024-06-28 07:45:50 +08:00
void resetTraffic() {
clashFFI.resetTraffic();
}
2024-04-30 23:38:49 +08:00
void startLog() {
clashFFI.startLog();
}
stopLog() {
clashFFI.stopLog();
}
startTun(int fd, int port) {
2024-07-13 16:36:08 +08:00
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);
2024-04-30 23:38:49 +08:00
}
requestGc() {
clashFFI.forceGc();
}
2024-04-30 23:38:49 +08:00
void stopTun() {
clashFFI.stopTun();
}
void setProcessMap(ProcessMapItem processMapItem) {
final processMapItemChar =
json.encode(processMapItem).toNativeUtf8().cast<Char>();
2024-06-28 07:45:50 +08:00
clashFFI.setProcessMap(processMapItemChar);
malloc.free(processMapItemChar);
}
void setFdMap(int fd) {
clashFFI.setFdMap(fd);
}
2024-07-13 16:36:08 +08:00
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));
}
2024-04-30 23:38:49 +08:00
List<Connection> getConnections() {
final connectionsDataRaw = clashFFI.getConnections();
final connectionsData =
2024-06-28 07:45:50 +08:00
json.decode(connectionsDataRaw.cast<Utf8>().toDartString()) as Map;
clashFFI.freeCString(connectionsDataRaw);
2024-04-30 23:38:49 +08:00
final connectionsRaw = connectionsData['connections'] as List? ?? [];
return connectionsRaw.map((e) => Connection.fromJson(e)).toList();
}
closeConnection(String id) {
2024-06-28 07:45:50 +08:00
final idChar = id.toNativeUtf8().cast<Char>();
clashFFI.closeConnection(idChar);
malloc.free(idChar);
}
closeConnections() {
clashFFI.closeConnections();
}
2024-04-30 23:38:49 +08:00
}
final clashCore = ClashCore();